From 6d3b0261b349068c20a1eab9771e2b435cfb1a52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 6 Aug 2023 00:42:34 +0200 Subject: [PATCH 01/62] Added first API endpoint --- .env | 4 + composer.json | 6 +- composer.lock | 362 +++++++++++++++++++++++++- config/bundles.php | 2 + config/packages/api_platform.yaml | 6 + config/packages/nelmio_cors.yaml | 10 + config/routes/api_platform.yaml | 4 + config/services.yaml | 15 ++ src/ApiResource/.gitignore | 0 src/ApiResource/PartDBInfo.php | 63 +++++ src/Controller/HomepageController.php | 23 +- src/Services/System/BannerHelper.php | 59 +++++ src/State/PartDBInfoProvider.php | 42 +++ symfony.lock | 25 ++ 14 files changed, 599 insertions(+), 22 deletions(-) create mode 100644 config/packages/api_platform.yaml create mode 100644 config/packages/nelmio_cors.yaml create mode 100644 config/routes/api_platform.yaml create mode 100644 src/ApiResource/.gitignore create mode 100644 src/ApiResource/PartDBInfo.php create mode 100644 src/Services/System/BannerHelper.php create mode 100644 src/State/PartDBInfoProvider.php diff --git a/.env b/.env index 22ba44cf..7b6e2625 100644 --- a/.env +++ b/.env @@ -206,3 +206,7 @@ APP_SECRET=a03498528f5a5fc089273ec9ae5b2849 # postgresql+advisory://db_user:db_password@localhost/db_name LOCK_DSN=flock ###< symfony/lock ### + +###> nelmio/cors-bundle ### +CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$' +###< nelmio/cors-bundle ### diff --git a/composer.json b/composer.json index a7844452..ae3f1d3c 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,7 @@ "ext-intl": "*", "ext-json": "*", "ext-mbstring": "*", + "api-platform/core": "^3.1", "beberlei/doctrineextensions": "^1.2", "brick/math": "^0.11.0", "composer/package-versions-deprecated": "^1.11.99.5", @@ -33,6 +34,7 @@ "liip/imagine-bundle": "^2.2", "nbgrp/onelogin-saml-bundle": "^1.3", "nelexa/zip": "^4.0", + "nelmio/cors-bundle": "^2.3", "nelmio/security-bundle": "^3.0", "nyholm/psr7": "^1.1", "ocramius/proxy-manager": "2.2.*", @@ -40,6 +42,7 @@ "part-db/label-fonts": "^1.0", "php-translation/symfony-bundle": "^0.14.0", "phpdocumentor/reflection-docblock": "^5.2", + "phpstan/phpdoc-parser": "^1.23", "s9e/text-formatter": "^2.1", "scheb/2fa-backup-code": "^6.8.0", "scheb/2fa-bundle": "^6.8.0", @@ -83,7 +86,8 @@ "twig/intl-extra": "^3.0", "twig/markdown-extra": "^3.0", "web-auth/webauthn-symfony-bundle": "^4.0.0", - "webmozart/assert": "^1.4" + "webmozart/assert": "^1.4", + "webonyx/graphql-php": "^15.6" }, "require-dev": { "dama/doctrine-test-bundle": "^7.0", diff --git a/composer.lock b/composer.lock index 971f75cd..fa777e8a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,177 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b1ad5ff23ca8d2cc779fed986a8e2c9a", + "content-hash": "1a2a25bc47002e077096b2aceaee7c7f", "packages": [ + { + "name": "api-platform/core", + "version": "v3.1.13", + "source": { + "type": "git", + "url": "https://github.com/api-platform/core.git", + "reference": "190bb4eabeafbe8e830af4a8ccac1feaf5b74e96" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/api-platform/core/zipball/190bb4eabeafbe8e830af4a8ccac1feaf5b74e96", + "reference": "190bb4eabeafbe8e830af4a8ccac1feaf5b74e96", + "shasum": "" + }, + "require": { + "doctrine/inflector": "^1.0 || ^2.0", + "php": ">=8.1", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^3.1", + "symfony/http-foundation": "^6.1", + "symfony/http-kernel": "^6.1", + "symfony/property-access": "^6.1", + "symfony/property-info": "^6.1", + "symfony/serializer": "^6.1", + "symfony/web-link": "^6.1", + "willdurand/negotiation": "^3.0" + }, + "conflict": { + "doctrine/common": "<3.2.2", + "doctrine/dbal": "<2.10", + "doctrine/mongodb-odm": "<2.4", + "doctrine/orm": "<2.14.0", + "doctrine/persistence": "<1.3", + "elasticsearch/elasticsearch": ">=8.0", + "phpspec/prophecy": "<1.15", + "phpunit/phpunit": "<9.5", + "symfony/service-contracts": "<3", + "symfony/var-exporter": "<6.1.1" + }, + "require-dev": { + "behat/behat": "^3.1", + "behat/mink": "^1.9", + "doctrine/cache": "^1.11 || ^2.1", + "doctrine/common": "^3.2.2", + "doctrine/dbal": "^3.4.0", + "doctrine/doctrine-bundle": "^1.12 || ^2.0", + "doctrine/mongodb-odm": "^2.2", + "doctrine/mongodb-odm-bundle": "^4.0", + "doctrine/orm": "^2.14", + "elasticsearch/elasticsearch": "^7.11.0", + "friends-of-behat/mink-browserkit-driver": "^1.3.1", + "friends-of-behat/mink-extension": "^2.2", + "friends-of-behat/symfony-extension": "^2.1", + "guzzlehttp/guzzle": "^6.0 || ^7.0", + "jangregor/phpstan-prophecy": "^1.0", + "justinrainbow/json-schema": "^5.2.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpdoc-parser": "^1.13", + "phpstan/phpstan": "^1.1", + "phpstan/phpstan-doctrine": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-symfony": "^1.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "ramsey/uuid": "^3.9.7 || ^4.0", + "ramsey/uuid-doctrine": "^1.4 || ^2.0", + "soyuka/contexts": "v3.3.9", + "soyuka/stubs-mongodb": "^1.0", + "symfony/asset": "^6.1", + "symfony/browser-kit": "^6.1", + "symfony/cache": "^6.1", + "symfony/config": "^6.1", + "symfony/console": "^6.1", + "symfony/css-selector": "^6.1", + "symfony/dependency-injection": "^6.1.12", + "symfony/doctrine-bridge": "^6.1", + "symfony/dom-crawler": "^6.1", + "symfony/error-handler": "^6.1", + "symfony/event-dispatcher": "^6.1", + "symfony/expression-language": "^6.1", + "symfony/finder": "^6.1", + "symfony/form": "^6.1", + "symfony/framework-bundle": "^6.1", + "symfony/http-client": "^6.1", + "symfony/intl": "^6.1", + "symfony/maker-bundle": "^1.24", + "symfony/mercure-bundle": "*", + "symfony/messenger": "^6.1", + "symfony/phpunit-bridge": "^6.1", + "symfony/routing": "^6.1", + "symfony/security-bundle": "^6.1", + "symfony/security-core": "^6.1", + "symfony/twig-bundle": "^6.1", + "symfony/uid": "^6.1", + "symfony/validator": "^6.1", + "symfony/web-profiler-bundle": "^6.1", + "symfony/yaml": "^6.1", + "twig/twig": "^1.42.3 || ^2.12 || ^3.0", + "webonyx/graphql-php": "^14.0 || ^15.0" + }, + "suggest": { + "doctrine/mongodb-odm-bundle": "To support MongoDB. Only versions 4.0 and later are supported.", + "elasticsearch/elasticsearch": "To support Elasticsearch.", + "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.", + "ramsey/uuid": "To support Ramsey's UUID identifiers.", + "symfony/cache": "To have metadata caching when using Symfony integration.", + "symfony/config": "To load XML configuration files.", + "symfony/expression-language": "To use authorization features.", + "symfony/http-client": "To use the HTTP cache invalidation system.", + "symfony/messenger": "To support messenger integration.", + "symfony/security": "To use authorization features.", + "symfony/twig-bundle": "To use the Swagger UI integration.", + "symfony/uid": "To support Symfony UUID/ULID identifiers.", + "symfony/web-profiler-bundle": "To use the data collector.", + "webonyx/graphql-php": "To support GraphQL." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.2.x-dev" + }, + "symfony": { + "require": "^6.1" + } + }, + "autoload": { + "psr-4": { + "ApiPlatform\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kévin Dunglas", + "email": "kevin@dunglas.fr", + "homepage": "https://dunglas.fr" + } + ], + "description": "Build a fully-featured hypermedia or GraphQL API in minutes!", + "homepage": "https://api-platform.com", + "keywords": [ + "Hydra", + "JSON-LD", + "api", + "graphql", + "hal", + "jsonapi", + "openapi", + "rest", + "swagger" + ], + "support": { + "issues": "https://github.com/api-platform/core/issues", + "source": "https://github.com/api-platform/core/tree/v3.1.13" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/api-platform/core", + "type": "tidelift" + } + ], + "time": "2023-08-03T16:44:18+00:00" + }, { "name": "beberlei/assert", "version": "v3.3.2", @@ -4100,6 +4269,68 @@ }, "time": "2022-06-17T11:17:46+00:00" }, + { + "name": "nelmio/cors-bundle", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/nelmio/NelmioCorsBundle.git", + "reference": "185d2c0ae50a3f0b628790170164d5f1c5b7c281" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nelmio/NelmioCorsBundle/zipball/185d2c0ae50a3f0b628790170164d5f1c5b7c281", + "reference": "185d2c0ae50a3f0b628790170164d5f1c5b7c281", + "shasum": "" + }, + "require": { + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/framework-bundle": "^4.4 || ^5.4 || ^6.0" + }, + "require-dev": { + "mockery/mockery": "^1.2", + "symfony/phpunit-bridge": "^4.4 || ^5.4 || ^6.0" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Nelmio\\CorsBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nelmio", + "homepage": "http://nelm.io" + }, + { + "name": "Symfony Community", + "homepage": "https://github.com/nelmio/NelmioCorsBundle/contributors" + } + ], + "description": "Adds CORS (Cross-Origin Resource Sharing) headers support in your Symfony application", + "keywords": [ + "api", + "cors", + "crossdomain" + ], + "support": { + "issues": "https://github.com/nelmio/NelmioCorsBundle/issues", + "source": "https://github.com/nelmio/NelmioCorsBundle/tree/2.3.1" + }, + "time": "2023-02-16T08:49:29+00:00" + }, { "name": "nelmio/security-bundle", "version": "v3.0.0", @@ -14128,6 +14359,135 @@ "source": "https://github.com/webmozarts/assert/tree/1.11.0" }, "time": "2022-06-03T18:03:27+00:00" + }, + { + "name": "webonyx/graphql-php", + "version": "v15.6.0", + "source": { + "type": "git", + "url": "https://github.com/webonyx/graphql-php.git", + "reference": "d950e2b542ee5c092c5d1375240b561282c06af1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/d950e2b542ee5c092c5d1375240b561282c06af1", + "reference": "d950e2b542ee5c092c5d1375240b561282c06af1", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "php": "^7.4 || ^8" + }, + "require-dev": { + "amphp/amp": "^2.6", + "amphp/http-server": "^2.1", + "dms/phpunit-arraysubset-asserts": "dev-master", + "ergebnis/composer-normalize": "^2.28", + "mll-lab/php-cs-fixer-config": "^5", + "nyholm/psr7": "^1.5", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "1.10.26", + "phpstan/phpstan-phpunit": "1.3.13", + "phpstan/phpstan-strict-rules": "1.5.1", + "phpunit/phpunit": "^9.5 || ^10", + "psr/http-message": "^1 || ^2", + "react/http": "^1.6", + "react/promise": "^2.9", + "rector/rector": "^0.17", + "symfony/polyfill-php81": "^1.23", + "symfony/var-exporter": "^5 || ^6", + "thecodingmachine/safe": "^1.3 || ^2" + }, + "suggest": { + "amphp/http-server": "To leverage async resolving with webserver on AMPHP platform", + "psr/http-message": "To use standard GraphQL server", + "react/promise": "To leverage async resolving on React PHP platform" + }, + "type": "library", + "autoload": { + "psr-4": { + "GraphQL\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP port of GraphQL reference implementation", + "homepage": "https://github.com/webonyx/graphql-php", + "keywords": [ + "api", + "graphql" + ], + "support": { + "issues": "https://github.com/webonyx/graphql-php/issues", + "source": "https://github.com/webonyx/graphql-php/tree/v15.6.0" + }, + "funding": [ + { + "url": "https://opencollective.com/webonyx-graphql-php", + "type": "open_collective" + } + ], + "time": "2023-08-04T09:43:22+00:00" + }, + { + "name": "willdurand/negotiation", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/willdurand/Negotiation.git", + "reference": "68e9ea0553ef6e2ee8db5c1d98829f111e623ec2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/willdurand/Negotiation/zipball/68e9ea0553ef6e2ee8db5c1d98829f111e623ec2", + "reference": "68e9ea0553ef6e2ee8db5c1d98829f111e623ec2", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Negotiation\\": "src/Negotiation" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "William Durand", + "email": "will+git@drnd.me" + } + ], + "description": "Content Negotiation tools for PHP provided as a standalone library.", + "homepage": "http://williamdurand.fr/Negotiation/", + "keywords": [ + "accept", + "content", + "format", + "header", + "negotiation" + ], + "support": { + "issues": "https://github.com/willdurand/Negotiation/issues", + "source": "https://github.com/willdurand/Negotiation/tree/3.1.0" + }, + "time": "2022-01-30T20:08:53+00:00" } ], "packages-dev": [ diff --git a/config/bundles.php b/config/bundles.php index 6545338d..70b10fa5 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -31,4 +31,6 @@ return [ Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true], Jbtronics\DompdfFontLoaderBundle\DompdfFontLoaderBundle::class => ['all' => true], KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle::class => ['all' => true], + Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true], + ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true], ]; diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml new file mode 100644 index 00000000..eb4bc686 --- /dev/null +++ b/config/packages/api_platform.yaml @@ -0,0 +1,6 @@ +api_platform: + + title: 'Part-DB API' + description: 'API of Part-DB' + + version: '0.1.0' \ No newline at end of file diff --git a/config/packages/nelmio_cors.yaml b/config/packages/nelmio_cors.yaml new file mode 100644 index 00000000..c7665081 --- /dev/null +++ b/config/packages/nelmio_cors.yaml @@ -0,0 +1,10 @@ +nelmio_cors: + defaults: + origin_regex: true + allow_origin: ['%env(CORS_ALLOW_ORIGIN)%'] + allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE'] + allow_headers: ['Content-Type', 'Authorization'] + expose_headers: ['Link'] + max_age: 3600 + paths: + '^/': null diff --git a/config/routes/api_platform.yaml b/config/routes/api_platform.yaml new file mode 100644 index 00000000..38f11cba --- /dev/null +++ b/config/routes/api_platform.yaml @@ -0,0 +1,4 @@ +api_platform: + resource: . + type: api_platform + prefix: /api diff --git a/config/services.yaml b/config/services.yaml index 24e6a6ac..8e6ee8af 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -277,6 +277,15 @@ services: $search_limit: '%env(int:PROVIDER_OCTOPART_SEARCH_LIMIT)%' $onlyAuthorizedSellers: '%env(bool:PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS)%' + #################################################################################################################### + # API system + #################################################################################################################### + App\State\PartDBInfoProvider: + arguments: + $default_uri: '%partdb.default_uri%' + $global_locale: '%partdb.locale%' + $global_timezone: '%partdb.timezone%' + #################################################################################################################### # Symfony overrides #################################################################################################################### @@ -319,6 +328,12 @@ services: arguments: $check_for_updates: '%partdb.check_for_updates%' + App\Services\System\BannerHelper: + arguments: + $partdb_banner: '%partdb.banner%' + $project_dir: '%kernel.project_dir%' + + #################################################################################################################### # Monolog #################################################################################################################### diff --git a/src/ApiResource/.gitignore b/src/ApiResource/.gitignore new file mode 100644 index 00000000..e69de29b diff --git a/src/ApiResource/PartDBInfo.php b/src/ApiResource/PartDBInfo.php new file mode 100644 index 00000000..f6356fcd --- /dev/null +++ b/src/ApiResource/PartDBInfo.php @@ -0,0 +1,63 @@ +. + */ + +declare(strict_types=1); + + +namespace App\ApiResource; + +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; +use App\State\PartDBInfoProvider; + +/** + * This class is used to provide various information about the system. + */ +#[ApiResource( + uriTemplate: '/info', + description: 'Basic information about Part-DB like version, title, etc.', + operations: [new Get()], + provider: PartDBInfoProvider::class +)] +class PartDBInfo +{ + public function __construct( + /** The installed Part-DB version */ + public readonly string $version, + /** The Git branch name of the Part-DB version (or null, if not installed via git) */ + public readonly string|null $git_branch, + /** The Git branch commit of the Part-DB version (or null, if not installed via git) */ + public readonly string|null $git_commit, + /** The name of this Part-DB instance */ + public readonly string $title, + /** The banner, shown on homepage (markdown encoded) */ + public readonly string $banner, + /** The configured default URI for Part-DB */ + public readonly string $default_uri, + /** The global timezone of this Part-DB */ + public readonly string $global_timezone, + /** The base currency of Part-DB, used as internal representation of monetary values */ + public readonly string $base_currency, + /** The configured default language of Part-DB */ + public readonly string $global_locale, + ) { + + } +} \ No newline at end of file diff --git a/src/Controller/HomepageController.php b/src/Controller/HomepageController.php index dc728465..f946d133 100644 --- a/src/Controller/HomepageController.php +++ b/src/Controller/HomepageController.php @@ -25,6 +25,7 @@ namespace App\Controller; use App\DataTables\LogDataTable; use App\Entity\Parts\Part; use App\Services\Misc\GitVersionInfo; +use App\Services\System\BannerHelper; use App\Services\System\UpdateAvailableManager; use Doctrine\ORM\EntityManagerInterface; use const DIRECTORY_SEPARATOR; @@ -38,29 +39,11 @@ use Symfony\Contracts\Cache\CacheInterface; class HomepageController extends AbstractController { - public function __construct(protected CacheInterface $cache, protected KernelInterface $kernel, protected DataTableFactory $dataTable) + public function __construct(private readonly DataTableFactory $dataTable, private readonly BannerHelper $bannerHelper) { } - public function getBanner(): string - { - $banner = $this->getParameter('partdb.banner'); - if (!is_string($banner)) { - throw new \RuntimeException('The parameter "partdb.banner" must be a string.'); - } - if (empty($banner)) { - $banner_path = $this->kernel->getProjectDir() - .DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'banner.md'; - $tmp = file_get_contents($banner_path); - if (false === $tmp) { - throw new \RuntimeException('The banner file could not be read.'); - } - $banner = $tmp; - } - - return $banner; - } #[Route(path: '/', name: 'homepage')] public function homepage(Request $request, GitVersionInfo $versionInfo, EntityManagerInterface $entityManager, @@ -94,7 +77,7 @@ class HomepageController extends AbstractController } return $this->render('homepage.html.twig', [ - 'banner' => $this->getBanner(), + 'banner' => $this->bannerHelper->getBanner(), 'git_branch' => $versionInfo->getGitBranchName(), 'git_commit' => $versionInfo->getGitCommitHash(), 'show_first_steps' => $show_first_steps, diff --git a/src/Services/System/BannerHelper.php b/src/Services/System/BannerHelper.php new file mode 100644 index 00000000..c0dbf600 --- /dev/null +++ b/src/Services/System/BannerHelper.php @@ -0,0 +1,59 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Services\System; + +/** + * Helper service to retrieve the banner of this Part-DB installation + */ +class BannerHelper +{ + public function __construct(private readonly string $project_dir, private readonly string $partdb_banner) + { + + } + + /** + * Retrieves the banner from either the env variable or the banner.md file. + * @return string + */ + public function getBanner(): string + { + $banner = $this->partdb_banner; + if (!is_string($banner)) { + throw new \RuntimeException('The parameter "partdb.banner" must be a string.'); + } + if (empty($banner)) { + $banner_path = $this->project_dir + .DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'banner.md'; + + $tmp = file_get_contents($banner_path); + if (false === $tmp) { + throw new \RuntimeException('The banner file could not be read.'); + } + $banner = $tmp; + } + + return $banner; + } +} \ No newline at end of file diff --git a/src/State/PartDBInfoProvider.php b/src/State/PartDBInfoProvider.php new file mode 100644 index 00000000..ad785e84 --- /dev/null +++ b/src/State/PartDBInfoProvider.php @@ -0,0 +1,42 @@ +versionManager->getVersion()->toString(), + git_branch: $this->gitVersionInfo->getGitBranchName(), + git_commit: $this->gitVersionInfo->getGitCommitHash(), + title: $this->partdb_title, + banner: $this->bannerHelper->getBanner(), + default_uri: $this->default_uri, + global_timezone: $this->global_timezone, + base_currency: $this->base_currency, + global_locale: $this->global_locale, + ); + } +} diff --git a/symfony.lock b/symfony.lock index d47e131c..4948e590 100644 --- a/symfony.lock +++ b/symfony.lock @@ -5,6 +5,19 @@ "amphp/byte-stream": { "version": "v1.6.1" }, + "api-platform/core": { + "version": "3.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "3.0", + "ref": "0330386d716d3eecc52ee5ac66976e733eb8f961" + }, + "files": [ + "./config/routes/api_platform.yaml", + "./src/ApiResource/.gitignore" + ] + }, "beberlei/assert": { "version": "v3.2.6" }, @@ -220,6 +233,18 @@ "nbgrp/onelogin-saml-bundle": { "version": "v1.3.2" }, + "nelmio/cors-bundle": { + "version": "2.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "1.5", + "ref": "6bea22e6c564fba3a1391615cada1437d0bde39c" + }, + "files": [ + "./config/packages/nelmio_cors.yaml" + ] + }, "nelmio/security-bundle": { "version": "2.4", "recipe": { From f5a15b23d67bd6a2dafcb926249a2d1fa4fbe082 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 6 Aug 2023 18:33:21 +0200 Subject: [PATCH 02/62] Improved PartDB Info endpoint --- src/ApiResource/PartDBInfo.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ApiResource/PartDBInfo.php b/src/ApiResource/PartDBInfo.php index f6356fcd..5fb2951d 100644 --- a/src/ApiResource/PartDBInfo.php +++ b/src/ApiResource/PartDBInfo.php @@ -23,19 +23,22 @@ declare(strict_types=1); namespace App\ApiResource; +use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; +use ApiPlatform\Serializer\Filter\PropertyFilter; use App\State\PartDBInfoProvider; /** * This class is used to provide various information about the system. */ #[ApiResource( - uriTemplate: '/info', + uriTemplate: '/info.{_format}', description: 'Basic information about Part-DB like version, title, etc.', operations: [new Get()], provider: PartDBInfoProvider::class )] +#[ApiFilter(PropertyFilter::class)] class PartDBInfo { public function __construct( From 676c8eeefb6cd1cb99c866bb15246d96cf399328 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 6 Aug 2023 20:18:51 +0200 Subject: [PATCH 03/62] Added basic API to access and edit manufacturer entities --- src/Entity/Base/AbstractCompany.php | 10 ++-- src/Entity/Base/AbstractDBElement.php | 5 +- src/Entity/Base/AbstractNamedDBElement.php | 2 +- src/Entity/Parts/Manufacturer.php | 23 ++++++++ src/Entity/UserSystem/Group.php | 1 + src/Entity/UserSystem/User.php | 59 +++++++++++++++---- .../StructuralElementDenormalizer.php | 5 ++ .../StructuralElementFromNameDenormalizer.php | 5 ++ .../StructuralElementNormalizer.php | 5 ++ .../ImportExportSystem/EntityExporter.php | 1 + .../ImportExportSystem/EntityImporter.php | 1 + 11 files changed, 96 insertions(+), 21 deletions(-) diff --git a/src/Entity/Base/AbstractCompany.php b/src/Entity/Base/AbstractCompany.php index ca6d1b88..ae0f47c6 100644 --- a/src/Entity/Base/AbstractCompany.php +++ b/src/Entity/Base/AbstractCompany.php @@ -43,21 +43,21 @@ abstract class AbstractCompany extends AbstractPartsContainingDBElement /** * @var string The address of the company */ - #[Groups(['full'])] + #[Groups(['full', 'company:read', 'company:write'])] #[ORM\Column(type: Types::STRING)] protected string $address = ''; /** * @var string The phone number of the company */ - #[Groups(['full'])] + #[Groups(['full', 'company:read', 'company:write'])] #[ORM\Column(type: Types::STRING)] protected string $phone_number = ''; /** * @var string The fax number of the company */ - #[Groups(['full'])] + #[Groups(['full', 'company:read', 'company:write'])] #[ORM\Column(type: Types::STRING)] protected string $fax_number = ''; @@ -65,7 +65,7 @@ abstract class AbstractCompany extends AbstractPartsContainingDBElement * @var string The email address of the company */ #[Assert\Email] - #[Groups(['full'])] + #[Groups(['full', 'company:read', 'company:write'])] #[ORM\Column(type: Types::STRING)] protected string $email_address = ''; @@ -73,7 +73,7 @@ abstract class AbstractCompany extends AbstractPartsContainingDBElement * @var string The website of the company */ #[Assert\Url] - #[Groups(['full'])] + #[Groups(['full', 'company:read', 'company:write'])] #[ORM\Column(type: Types::STRING)] protected string $website = ''; diff --git a/src/Entity/Base/AbstractDBElement.php b/src/Entity/Base/AbstractDBElement.php index 30fcab06..7fceccf4 100644 --- a/src/Entity/Base/AbstractDBElement.php +++ b/src/Entity/Base/AbstractDBElement.php @@ -37,6 +37,7 @@ use App\Entity\Attachments\ProjectAttachment; use App\Entity\Attachments\StorelocationAttachment; use App\Entity\Attachments\SupplierAttachment; use App\Entity\Attachments\UserAttachment; +use App\Entity\Parameters\AbstractParameter; use App\Entity\Parts\Category; use App\Entity\ProjectSystem\Project; use App\Entity\ProjectSystem\ProjectBOMEntry; @@ -66,14 +67,14 @@ 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, 'project_attachment' => ProjectAttachment::class, 'storelocation_attachment' => StorelocationAttachment::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' => 'App\Entity\PriceInformation\Pricedetail', 'storelocation' => Storelocation::class, 'part_lot' => PartLot::class, 'currency' => Currency::class, 'measurement_unit' => MeasurementUnit::class, 'parameter' => 'App\Entity\Parts\AbstractParameter', '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' => StorelocationAttachment::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' => 'App\Entity\PriceInformation\Pricedetail', 'storelocation' => Storelocation::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 { /** @var int|null The Identification number for this part. This value is unique for the element in this table. * Null if the element is not saved to DB yet. */ - #[Groups(['full'])] + #[Groups(['full', 'api:basic:read'])] #[ORM\Column(type: Types::INTEGER)] #[ORM\Id] #[ORM\GeneratedValue] diff --git a/src/Entity/Base/AbstractNamedDBElement.php b/src/Entity/Base/AbstractNamedDBElement.php index e5e30441..c7f5f872 100644 --- a/src/Entity/Base/AbstractNamedDBElement.php +++ b/src/Entity/Base/AbstractNamedDBElement.php @@ -43,7 +43,7 @@ abstract class AbstractNamedDBElement extends AbstractDBElement implements Named * @var string the name of this element */ #[Assert\NotBlank] - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'api:basic:read', 'api:basic:write'])] #[ORM\Column(type: Types::STRING)] protected string $name = ''; diff --git a/src/Entity/Parts/Manufacturer.php b/src/Entity/Parts/Manufacturer.php index 418b4084..70ea1b6a 100644 --- a/src/Entity/Parts/Manufacturer.php +++ b/src/Entity/Parts/Manufacturer.php @@ -22,6 +22,10 @@ declare(strict_types=1); namespace App\Entity\Parts; +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Link; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\Parts\ManufacturerRepository; @@ -32,6 +36,7 @@ use App\Entity\Base\AbstractCompany; use App\Entity\Parameters\ManufacturerParameter; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; /** @@ -43,10 +48,24 @@ use Symfony\Component\Validator\Constraints as Assert; #[ORM\Table('`manufacturers`')] #[ORM\Index(name: 'manufacturer_name', columns: ['name'])] #[ORM\Index(name: 'manufacturer_idx_parent_name', columns: ['parent_id', 'name'])] +#[ApiResource( + normalizationContext: ['groups' => ['manufacturer:read', 'company:read', 'api:basic:read']], + denormalizationContext: ['groups' => ['manufacturer:write', 'company:write', 'api:basic:write']], +)] +#[ApiResource( + uriTemplate: '/manufacturers/{id}/children.{_format}', + operations: [new GetCollection()], + uriVariables: [ + 'id' => new Link(fromClass: Manufacturer::class, fromProperty: 'children') + ], + normalizationContext: ['groups' => ['manufacturer:read', 'company:read', 'api:basic:read']] +)] class Manufacturer extends AbstractCompany { #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] #[ORM\JoinColumn(name: 'parent_id')] + #[Groups(['manufacturer:read', 'manufacturer:write'])] + #[ApiProperty(readableLink: false, writableLink: false)] protected ?AbstractStructuralDBElement $parent = null; #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')] @@ -59,10 +78,14 @@ class Manufacturer extends AbstractCompany #[Assert\Valid] #[ORM\OneToMany(targetEntity: ManufacturerAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['name' => 'ASC'])] + #[Groups(['manufacturer:read', 'manufacturer:write'])] + #[ApiProperty(readableLink: false, writableLink: true)] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: ManufacturerAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] + #[Groups(['manufacturer:read', 'manufacturer:write'])] + #[ApiProperty(readableLink: false, writableLink: true)] protected ?Attachment $master_picture_attachment = null; /** @var Collection diff --git a/src/Entity/UserSystem/Group.php b/src/Entity/UserSystem/Group.php index 01e79498..352c0c94 100644 --- a/src/Entity/UserSystem/Group.php +++ b/src/Entity/UserSystem/Group.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\UserSystem; +use ApiPlatform\Metadata\ApiResource; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentTypeAttachment; use App\Validator\Constraints\NoLockout; diff --git a/src/Entity/UserSystem/User.php b/src/Entity/UserSystem/User.php index b1dc63ad..6a778e51 100644 --- a/src/Entity/UserSystem/User.php +++ b/src/Entity/UserSystem/User.php @@ -22,6 +22,14 @@ declare(strict_types=1); namespace App\Entity\UserSystem; +use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Serializer\Filter\PropertyFilter; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\UserRepository; @@ -72,7 +80,16 @@ use Jbtronics\TFAWebauthn\Model\TwoFactorInterface as WebauthnTwoFactorInterface #[ORM\AttributeOverrides([ new ORM\AttributeOverride(name: 'name', column: new ORM\Column(type: Types::STRING, length: 180, unique: true)) ])] - +#[ApiResource( + shortName: 'User', + operations: [ + new Get(), + new GetCollection(), + ], + normalizationContext: ['groups' => ['user:read']], +)] +#[ApiFilter(PropertyFilter::class)] +#[ApiFilter(SearchFilter::class, properties: ['name' => 'exact', 'email' => 'exact'])] #[NoLockout()] class User extends AttachmentContainingDBElement implements UserInterface, HasPermissionsInterface, TwoFactorInterface, BackupCodeInterface, TrustedDeviceInterface, WebauthnTwoFactorInterface, PreferredProviderInterface, PasswordAuthenticatedUserInterface, SamlUserInterface @@ -84,17 +101,26 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe */ final public const ID_ANONYMOUS = 1; + #[Groups(['user:read'])] + protected ?int $id; + + #[Groups(['user:read'])] + protected ?\DateTimeInterface $lastModified = null; + + #[Groups(['user:read'])] + protected ?\DateTimeInterface $createdAt = null; + /** * @var bool Determines if the user is disabled (user can not log in) */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'user:read'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $disabled = false; /** * @var string|null The theme */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'user:read'])] #[ORM\Column(type: Types::STRING, name: 'config_theme', nullable: true)] #[ValidTheme()] protected ?string $theme = null; @@ -112,9 +138,9 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe protected string $instock_comment_w = ''; /** - * @var string A self-description of the user + * @var string A self-description of the user as markdown text */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'user:read'])] #[ORM\Column(type: Types::TEXT)] protected string $aboutMe = ''; @@ -133,10 +159,11 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe * @var Group|null the group this user belongs to * DO NOT PUT A fetch eager here! Otherwise, you can not unset the group of a user! This seems to be some kind of bug in doctrine. Maybe this is fixed in future versions. */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'user:read'])] #[ORM\ManyToOne(targetEntity: Group::class, inversedBy: 'users')] #[ORM\JoinColumn(name: 'group_id')] #[Selectable] + #[ApiProperty(readableLink: true, writableLink: false)] protected ?Group $group = null; /** @@ -149,7 +176,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe * @var string|null The timezone the user prefers */ #[Assert\Timezone] - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'user:read'])] #[ORM\Column(type: Types::STRING, name: 'config_timezone', nullable: true)] protected ?string $timezone = ''; @@ -157,7 +184,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe * @var string|null The language/locale the user prefers */ #[Assert\Language] - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'user:read'])] #[ORM\Column(type: Types::STRING, name: 'config_language', nullable: true)] protected ?string $language = ''; @@ -165,7 +192,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe * @var string|null The email address of the user */ #[Assert\Email] - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'user:read'])] #[ORM\Column(type: Types::STRING, length: 255, nullable: true)] protected ?string $email = ''; @@ -173,33 +200,34 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe * @var bool True if the user wants to show his email address on his (public) profile */ #[ORM\Column(type: Types::BOOLEAN, options: ['default' => false])] + #[Groups(['full', 'import', 'user:read'])] protected bool $show_email_on_profile = false; /** * @var string|null The department the user is working */ - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'user:read'])] #[ORM\Column(type: Types::STRING, length: 255, nullable: true)] protected ?string $department = ''; /** * @var string|null The last name of the User */ - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'user:read'])] #[ORM\Column(type: Types::STRING, length: 255, nullable: true)] protected ?string $last_name = ''; /** * @var string|null The first name of the User */ - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'user:read'])] #[ORM\Column(type: Types::STRING, length: 255, nullable: true)] protected ?string $first_name = ''; /** * @var bool True if the user needs to change password after log in */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'user:read'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $need_pw_change = true; @@ -211,6 +239,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe #[Assert\NotBlank] #[Assert\Regex('/^[\w\.\+\-\$]+$/', message: 'user.invalid_username')] + #[Groups(['user:read'])] protected string $name = ''; /** @@ -224,10 +253,13 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe */ #[ORM\OneToMany(mappedBy: 'element', targetEntity: UserAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['name' => 'ASC'])] + #[Groups(['user:read'])] + #[ApiProperty(readableLink: false, writableLink: false)] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: UserAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] + #[Groups(['user:read'])] protected ?Attachment $master_picture_attachment = null; /** @var \DateTimeInterface|null The time when the backup codes were generated @@ -501,6 +533,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe * * @return string a string with the full name of this user */ + #[Groups(['user:read'])] public function getFullName(bool $including_username = false): string { $tmp = $this->getFirstName(); diff --git a/src/Serializer/StructuralElementDenormalizer.php b/src/Serializer/StructuralElementDenormalizer.php index ce6f91ca..2e40070d 100644 --- a/src/Serializer/StructuralElementDenormalizer.php +++ b/src/Serializer/StructuralElementDenormalizer.php @@ -49,6 +49,11 @@ class StructuralElementDenormalizer implements DenormalizerInterface public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool { + //Only denormalize if we are doing a file import operation + if (!($context['partdb_import'] ?? false)) { + return false; + } + return is_array($data) && is_subclass_of($type, AbstractStructuralDBElement::class) //Only denormalize if we are doing a file import operation diff --git a/src/Serializer/StructuralElementFromNameDenormalizer.php b/src/Serializer/StructuralElementFromNameDenormalizer.php index 4277fed4..51022aa1 100644 --- a/src/Serializer/StructuralElementFromNameDenormalizer.php +++ b/src/Serializer/StructuralElementFromNameDenormalizer.php @@ -39,6 +39,11 @@ class StructuralElementFromNameDenormalizer implements DenormalizerInterface public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool { + //Only denormalize if we are doing a file import operation + if (!($context['partdb_import'] ?? false)) { + return false; + } + return is_string($data) && is_subclass_of($type, AbstractStructuralDBElement::class); } diff --git a/src/Serializer/StructuralElementNormalizer.php b/src/Serializer/StructuralElementNormalizer.php index fc64aec0..e9d41d8c 100644 --- a/src/Serializer/StructuralElementNormalizer.php +++ b/src/Serializer/StructuralElementNormalizer.php @@ -43,6 +43,11 @@ class StructuralElementNormalizer implements NormalizerInterface public function supportsNormalization($data, string $format = null, array $context = []): bool { + //Only normalize if we are doing a file export operation + if (!($context['partdb_export'] ?? false)) { + return false; + } + return $data instanceof AbstractStructuralDBElement; } diff --git a/src/Services/ImportExportSystem/EntityExporter.php b/src/Services/ImportExportSystem/EntityExporter.php index 50b6b7cc..c99a3672 100644 --- a/src/Services/ImportExportSystem/EntityExporter.php +++ b/src/Services/ImportExportSystem/EntityExporter.php @@ -95,6 +95,7 @@ class EntityExporter 'as_collection' => true, 'csv_delimiter' => $options['csv_delimiter'], 'xml_root_node_name' => 'PartDBExport', + 'partdb_export' => true, ] ); } diff --git a/src/Services/ImportExportSystem/EntityImporter.php b/src/Services/ImportExportSystem/EntityImporter.php index 0b4e0f17..8a4ba0cd 100644 --- a/src/Services/ImportExportSystem/EntityImporter.php +++ b/src/Services/ImportExportSystem/EntityImporter.php @@ -156,6 +156,7 @@ class EntityImporter 'csv_delimiter' => $options['csv_delimiter'], 'create_unknown_datastructures' => $options['create_unknown_datastructures'], 'path_delimiter' => $options['path_delimiter'], + 'partdb_import' => true, ]); //Ensure we have an array of entity elements. From 6b5c51bdc53b7243d54569a82b475b04b542ad84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 6 Aug 2023 20:50:19 +0200 Subject: [PATCH 04/62] Improved schema infos of Manufacturer endpoints --- src/ApiResource/PartDBInfo.php | 2 +- src/Entity/Base/AbstractCompany.php | 5 +++++ src/Entity/Base/AbstractStructuralDBElement.php | 4 ++++ src/Entity/Parts/Manufacturer.php | 11 +++++++---- src/Entity/UserSystem/User.php | 6 +++--- 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/ApiResource/PartDBInfo.php b/src/ApiResource/PartDBInfo.php index 5fb2951d..9a06f262 100644 --- a/src/ApiResource/PartDBInfo.php +++ b/src/ApiResource/PartDBInfo.php @@ -35,7 +35,7 @@ use App\State\PartDBInfoProvider; #[ApiResource( uriTemplate: '/info.{_format}', description: 'Basic information about Part-DB like version, title, etc.', - operations: [new Get()], + operations: [new Get(openapiContext: ['summary' => 'Get basic information about the installed Part-DB instance.'])], provider: PartDBInfoProvider::class )] #[ApiFilter(PropertyFilter::class)] diff --git a/src/Entity/Base/AbstractCompany.php b/src/Entity/Base/AbstractCompany.php index ae0f47c6..b552d705 100644 --- a/src/Entity/Base/AbstractCompany.php +++ b/src/Entity/Base/AbstractCompany.php @@ -40,6 +40,11 @@ use Symfony\Component\Validator\Constraints as Assert; #[ORM\MappedSuperclass] abstract class AbstractCompany extends AbstractPartsContainingDBElement { + #[Groups(['company:read'])] + protected ?\DateTimeInterface $addedDate = null; + #[Groups(['company:read'])] + protected ?\DateTimeInterface $lastModified = null; + /** * @var string The address of the company */ diff --git a/src/Entity/Base/AbstractStructuralDBElement.php b/src/Entity/Base/AbstractStructuralDBElement.php index 5c4103d8..59d374c8 100644 --- a/src/Entity/Base/AbstractStructuralDBElement.php +++ b/src/Entity/Base/AbstractStructuralDBElement.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Base; +use ApiPlatform\Metadata\ApiProperty; use App\Entity\Attachments\Attachment; use App\Entity\Parameters\AbstractParameter; use App\Repository\StructuralDBElementRepository; @@ -31,6 +32,7 @@ use Doctrine\DBAL\Types\Types; use App\Entity\Attachments\AttachmentContainingDBElement; use App\Entity\Parameters\ParametersTrait; use App\Validator\Constraints\NoneOfItsChildren; +use Symfony\Component\Serializer\Annotation\SerializedName; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints\Valid; use function count; @@ -261,6 +263,8 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement * * @return string the full path (incl. the name of this element), delimited by $delimiter */ + #[Groups(['api:basic:read'])] + #[SerializedName('full_path')] public function getFullPath(string $delimiter = self::PATH_DELIMITER_ARROW): string { if ($this->full_path_strings === []) { diff --git a/src/Entity/Parts/Manufacturer.php b/src/Entity/Parts/Manufacturer.php index 70ea1b6a..b83024ef 100644 --- a/src/Entity/Parts/Manufacturer.php +++ b/src/Entity/Parts/Manufacturer.php @@ -22,10 +22,12 @@ declare(strict_types=1); namespace App\Entity\Parts; +use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Link; +use ApiPlatform\Serializer\Filter\PropertyFilter; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\Parts\ManufacturerRepository; @@ -49,17 +51,18 @@ use Symfony\Component\Validator\Constraints as Assert; #[ORM\Index(name: 'manufacturer_name', columns: ['name'])] #[ORM\Index(name: 'manufacturer_idx_parent_name', columns: ['parent_id', 'name'])] #[ApiResource( - normalizationContext: ['groups' => ['manufacturer:read', 'company:read', 'api:basic:read']], - denormalizationContext: ['groups' => ['manufacturer:write', 'company:write', 'api:basic:write']], + normalizationContext: ['groups' => ['manufacturer:read', 'company:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['manufacturer:write', 'company:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], )] #[ApiResource( uriTemplate: '/manufacturers/{id}/children.{_format}', - operations: [new GetCollection()], + operations: [new GetCollection(openapiContext: ['summary' => 'Retrieves the children elements of a manufacturer.'])], uriVariables: [ 'id' => new Link(fromClass: Manufacturer::class, fromProperty: 'children') ], - normalizationContext: ['groups' => ['manufacturer:read', 'company:read', 'api:basic:read']] + normalizationContext: ['groups' => ['manufacturer:read', 'company:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] )] +#[ApiFilter(PropertyFilter::class)] class Manufacturer extends AbstractCompany { #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] diff --git a/src/Entity/UserSystem/User.php b/src/Entity/UserSystem/User.php index 6a778e51..7bd22924 100644 --- a/src/Entity/UserSystem/User.php +++ b/src/Entity/UserSystem/User.php @@ -83,10 +83,10 @@ use Jbtronics\TFAWebauthn\Model\TwoFactorInterface as WebauthnTwoFactorInterface #[ApiResource( shortName: 'User', operations: [ - new Get(), - new GetCollection(), + new Get(openapiContext: ['summary' => 'Get a specific user.']), + new GetCollection(openapiContext: ['summary' => 'Get all users defined in the system.']), ], - normalizationContext: ['groups' => ['user:read']], + normalizationContext: ['groups' => ['user:read'], 'openapi_definition_name' => 'Read'], )] #[ApiFilter(PropertyFilter::class)] #[ApiFilter(SearchFilter::class, properties: ['name' => 'exact', 'email' => 'exact'])] From 3486957447887b150c4f55666a312c1bc2ba36c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 6 Aug 2023 20:57:32 +0200 Subject: [PATCH 05/62] Added endpoint for suppliers --- src/Entity/Parts/Manufacturer.php | 2 ++ src/Entity/Parts/Supplier.php | 30 +++++++++++++++++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/Entity/Parts/Manufacturer.php b/src/Entity/Parts/Manufacturer.php index b83024ef..0eb9653a 100644 --- a/src/Entity/Parts/Manufacturer.php +++ b/src/Entity/Parts/Manufacturer.php @@ -96,6 +96,8 @@ class Manufacturer extends AbstractCompany #[Assert\Valid] #[ORM\OneToMany(targetEntity: ManufacturerParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[Groups(['manufacturer:read', 'manufacturer:write'])] + #[ApiProperty(readableLink: false, writableLink: true)] protected Collection $parameters; public function __construct() { diff --git a/src/Entity/Parts/Supplier.php b/src/Entity/Parts/Supplier.php index 4dca8f36..94986435 100644 --- a/src/Entity/Parts/Supplier.php +++ b/src/Entity/Parts/Supplier.php @@ -22,6 +22,12 @@ declare(strict_types=1); namespace App\Entity\Parts; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Link; +use ApiPlatform\Serializer\Filter\PropertyFilter; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\Parts\SupplierRepository; @@ -49,6 +55,19 @@ use Symfony\Component\Validator\Constraints as Assert; #[ORM\Table('`suppliers`')] #[ORM\Index(name: 'supplier_idx_name', columns: ['name'])] #[ORM\Index(name: 'supplier_idx_parent_name', columns: ['parent_id', 'name'])] +#[ApiResource( + normalizationContext: ['groups' => ['supplier:read', 'company:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['supplier:write', 'company:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiResource( + uriTemplate: '/suppliers/{id}/children.{_format}', + operations: [new GetCollection(openapiContext: ['summary' => 'Retrieves the children elements of a supplier'])], + uriVariables: [ + 'id' => new Link(fromClass: Supplier::class, fromProperty: 'children') + ], + normalizationContext: ['groups' => ['supplier:read', 'company:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] +)] +#[ApiFilter(PropertyFilter::class)] class Supplier extends AbstractCompany { #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')] @@ -57,11 +76,10 @@ class Supplier extends AbstractCompany #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] #[ORM\JoinColumn(name: 'parent_id')] + #[Groups(['supplier:read', 'supplier:write'])] + #[ApiProperty(readableLink: false, writableLink: false)] protected ?AbstractStructuralDBElement $parent = null; - /** - * @var Collection|Orderdetail[] - */ /** * @var Collection|Orderdetail[] */ @@ -91,10 +109,14 @@ class Supplier extends AbstractCompany #[Assert\Valid] #[ORM\OneToMany(targetEntity: SupplierAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['name' => 'ASC'])] + #[Groups(['supplier:read', 'supplier:write'])] + #[ApiProperty(readableLink: false, writableLink: true)] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: SupplierAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] + #[Groups(['supplier:read', 'supplier:write'])] + #[ApiProperty(readableLink: false, writableLink: true)] protected ?Attachment $master_picture_attachment = null; /** @var Collection @@ -102,6 +124,8 @@ class Supplier extends AbstractCompany #[Assert\Valid] #[ORM\OneToMany(targetEntity: SupplierParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[Groups(['supplier:read', 'supplier:write'])] + #[ApiProperty(readableLink: false, writableLink: true)] protected Collection $parameters; /** From 3bc6e618694650bd4e267a5c5e5c3e292168a662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Tue, 15 Aug 2023 23:36:56 +0200 Subject: [PATCH 06/62] Added very basic system to add API tokens to an user --- migrations/Version20230815212203.php | 33 +++++ src/Controller/UserSettingsController.php | 44 ++++++ src/Entity/UserSystem/ApiToken.php | 125 ++++++++++++++++++ src/Entity/UserSystem/ApiTokenType.php | 56 ++++++++ src/Entity/UserSystem/User.php | 41 +++++- .../UserSystem/ApiTokenRepository.php | 32 +++++ templates/users/api_token_create.html.twig | 16 +++ tests/Entity/UserSystem/ApiTokenTypeTest.php | 50 +++++++ 8 files changed, 395 insertions(+), 2 deletions(-) create mode 100644 migrations/Version20230815212203.php create mode 100644 src/Entity/UserSystem/ApiToken.php create mode 100644 src/Entity/UserSystem/ApiTokenType.php create mode 100644 src/Repository/UserSystem/ApiTokenRepository.php create mode 100644 templates/users/api_token_create.html.twig create mode 100644 tests/Entity/UserSystem/ApiTokenTypeTest.php diff --git a/migrations/Version20230815212203.php b/migrations/Version20230815212203.php new file mode 100644 index 00000000..1a6c372e --- /dev/null +++ b/migrations/Version20230815212203.php @@ -0,0 +1,33 @@ +addSql('CREATE TABLE api_tokens (id INT AUTO_INCREMENT NOT NULL, user_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, valid_until DATETIME DEFAULT NULL, token VARCHAR(68) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, UNIQUE INDEX UNIQ_2CAD560E5F37A13B (token), INDEX IDX_2CAD560EA76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE api_tokens ADD CONSTRAINT FK_2CAD560EA76ED395 FOREIGN KEY (user_id) REFERENCES `users` (id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE api_tokens DROP FOREIGN KEY FK_2CAD560EA76ED395'); + $this->addSql('DROP TABLE api_tokens'); + } +} diff --git a/src/Controller/UserSettingsController.php b/src/Controller/UserSettingsController.php index 704bacb7..1bc9318e 100644 --- a/src/Controller/UserSettingsController.php +++ b/src/Controller/UserSettingsController.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace App\Controller; use App\Entity\Attachments\Attachment; +use App\Entity\UserSystem\ApiToken; use App\Entity\UserSystem\U2FKey; use App\Entity\UserSystem\User; use App\Entity\UserSystem\WebauthnKey; @@ -39,6 +40,7 @@ use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticator use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Form\Extension\Core\Type\DateTimeType; use Symfony\Component\Form\Extension\Core\Type\PasswordType; use Symfony\Component\Form\Extension\Core\Type\RepeatedType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; @@ -395,4 +397,46 @@ class UserSettingsController extends AbstractController ], ]); } + + /** + * @return Response + */ + #[Route('/api_token/create', name: 'user_api_token_create')] + public function addApiToken(Request $request, EntityManagerInterface $entityManager): Response + { + $token = new ApiToken(); + + $secret = null; + + $form = $this->createFormBuilder($token) + ->add('name', TextType::class, [ + 'label' => 'user.api_token.name', + ]) + ->add('valid_until', DateTimeType::class, [ + 'label' => 'user.api_token.valid_until', + 'widget' => 'single_text', + 'required' => false, + 'html5' => true + ]) + ->add('submit', SubmitType::class, [ + 'label' => 'save', + ]) + ->getForm(); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $token->setUser($this->getUser()); + $entityManager->persist($token); + $entityManager->flush(); + + $secret = $token->getToken(); + } + + return $this->render('users/api_token_create.html.twig', [ + 'token' => $token, + 'form' => $form, + 'secret' => $secret, + ]); + } } diff --git a/src/Entity/UserSystem/ApiToken.php b/src/Entity/UserSystem/ApiToken.php new file mode 100644 index 00000000..f895febf --- /dev/null +++ b/src/Entity/UserSystem/ApiToken.php @@ -0,0 +1,125 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Entity\UserSystem; + +use App\Entity\Base\AbstractNamedDBElement; +use App\Entity\Base\TimestampTrait; +use App\Repository\UserSystem\ApiTokenRepository; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; +use Symfony\Component\Validator\Constraints\NotBlank; + +#[ORM\Entity(repositoryClass: ApiTokenRepository::class)] +#[ORM\Table(name: 'api_tokens')] +#[ORM\HasLifecycleCallbacks] +#[UniqueEntity(fields: ['name', 'user'])] +class ApiToken +{ + + use TimestampTrait; + + #[ORM\Id] + #[ORM\Column(type: Types::INTEGER)] + #[ORM\GeneratedValue] + protected int $id; + + #[ORM\Column(type: Types::STRING)] + #[NotBlank] + protected string $name = ''; + + #[ORM\ManyToOne(inversedBy: 'api_tokens')] + private ?User $user = null; + + #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)] + private ?\DateTimeInterface $valid_until = null; + + #[ORM\Column(length: 68, unique: true)] + private string $token; + + public function __construct(ApiTokenType $tokenType = ApiTokenType::PERSONAL_ACCESS_TOKEN) + { + // Generate a rondom token on creation. The tokenType is 3 characters long (plus underscore), so the token is 68 characters long. + $this->token = $tokenType->getTokenPrefix() . bin2hex(random_bytes(32)); + } + + public function getTokenType(): ApiTokenType + { + return ApiTokenType::getTypeFromToken($this->token); + } + + public function getUser(): ?User + { + return $this->user; + } + + public function setUser(?User $user): ApiToken + { + $this->user = $user; + return $this; + } + + public function getValidUntil(): ?\DateTimeInterface + { + return $this->valid_until; + } + + /** + * Checks if the token is still valid. + * @return bool + */ + public function isValid(): bool + { + return $this->valid_until === null || $this->valid_until > new \DateTime(); + } + + public function setValidUntil(?\DateTimeInterface $valid_until): ApiToken + { + $this->valid_until = $valid_until; + return $this; + } + + public function getToken(): string + { + return $this->token; + } + + public function getId(): int + { + return $this->id; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): ApiToken + { + $this->name = $name; + return $this; + } + + +} \ No newline at end of file diff --git a/src/Entity/UserSystem/ApiTokenType.php b/src/Entity/UserSystem/ApiTokenType.php new file mode 100644 index 00000000..f8beb378 --- /dev/null +++ b/src/Entity/UserSystem/ApiTokenType.php @@ -0,0 +1,56 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Entity\UserSystem; + +/** + * The type of ApiToken. + * The enum value is the prefix of the token. It must be 3 characters long. + */ +enum ApiTokenType: string +{ + case PERSONAL_ACCESS_TOKEN = 'tcp'; + + /** + * Get the prefix of the token including the underscore + * @return string + */ + public function getTokenPrefix(): string + { + return $this->value . '_'; + } + + /** + * Get the type from the token prefix + * @param string $api_token + * @return ApiTokenType + */ + public static function getTypeFromToken(string $api_token): ApiTokenType + { + $parts = explode('_', $api_token); + if (count($parts) !== 2) { + throw new \InvalidArgumentException('Invalid token format'); + } + return self::from($parts[0]); + } +} diff --git a/src/Entity/UserSystem/User.php b/src/Entity/UserSystem/User.php index 7bd22924..cfc144d8 100644 --- a/src/Entity/UserSystem/User.php +++ b/src/Entity/UserSystem/User.php @@ -279,6 +279,12 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe #[ORM\OneToMany(mappedBy: 'user', targetEntity: WebauthnKey::class, cascade: ['REMOVE'], fetch: 'EXTRA_LAZY', orphanRemoval: true)] protected Collection $webauthn_keys; + /** + * @var Collection + */ + #[ORM\OneToMany(mappedBy: 'user', targetEntity: ApiToken::class, cascade: ['REMOVE'], fetch: 'EXTRA_LAZY', orphanRemoval: true)] + private Collection $api_tokens; + /** * @var Currency|null The currency the user wants to see prices in. * Dont use fetch=EAGER here, this will cause problems with setting the currency setting. @@ -316,6 +322,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe $this->permissions = new PermissionData(); $this->u2fKeys = new ArrayCollection(); $this->webauthn_keys = new ArrayCollection(); + $this->api_tokens = new ArrayCollection(); } /** @@ -969,8 +976,6 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe return $this; } - - public function setSamlAttributes(array $attributes): void { //When mail attribute exists, set it @@ -1000,4 +1005,36 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe $this->setEmail($attributes['urn:oid:1.2.840.113549.1.9.1'][0]); } } + + /** + * Return all API tokens of the user. + * @return Collection + */ + public function getApiTokens(): Collection + { + return $this->api_tokens; + } + + /** + * Add an API token to the user. + * @param ApiToken $apiToken + * @return void + */ + public function addApiToken(ApiToken $apiToken): void + { + $this->api_tokens->add($apiToken); + } + + /** + * Remove an API token from the user. + * @param ApiToken $apiToken + * @return void + */ + public function removeApiToken(ApiToken $apiToken): void + { + $this->api_tokens->removeElement($apiToken); + } + + + } diff --git a/src/Repository/UserSystem/ApiTokenRepository.php b/src/Repository/UserSystem/ApiTokenRepository.php new file mode 100644 index 00000000..7e11ee53 --- /dev/null +++ b/src/Repository/UserSystem/ApiTokenRepository.php @@ -0,0 +1,32 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Repository\UserSystem; + +use App\Repository\NamedDBElementRepository; +use Doctrine\ORM\EntityRepository; + +class ApiTokenRepository extends EntityRepository +{ + +} \ No newline at end of file diff --git a/templates/users/api_token_create.html.twig b/templates/users/api_token_create.html.twig new file mode 100644 index 00000000..57b86708 --- /dev/null +++ b/templates/users/api_token_create.html.twig @@ -0,0 +1,16 @@ +{% extends "main_card.html.twig" %} + +{% block card_title %}Add API token{% endblock %} + +{% block card_content %} + {# Show API secret after submit #} + + {% if secret is not null %} +
+ Your API token is: {{ secret }}
+ Please save it. You wont be able to see it again! +
+ {% endif %} + + {{ form(form) }} +{% endblock %} \ No newline at end of file diff --git a/tests/Entity/UserSystem/ApiTokenTypeTest.php b/tests/Entity/UserSystem/ApiTokenTypeTest.php new file mode 100644 index 00000000..d504f39d --- /dev/null +++ b/tests/Entity/UserSystem/ApiTokenTypeTest.php @@ -0,0 +1,50 @@ +. + */ + +namespace App\Tests\Entity\UserSystem; + +use App\Entity\UserSystem\ApiTokenType; +use PHPUnit\Framework\TestCase; + +class ApiTokenTypeTest extends TestCase +{ + + public function testGetTokenPrefix(): void + { + $this->assertEquals('tcp_', ApiTokenType::PERSONAL_ACCESS_TOKEN->getTokenPrefix()); + } + + public function testGetTypeFromToken(): void + { + $this->assertEquals(ApiTokenType::PERSONAL_ACCESS_TOKEN, ApiTokenType::getTypeFromToken('tcp_123')); + } + + public function testGetTypeFromTokenInvalid(): void + { + $this->expectException(\InvalidArgumentException::class); + ApiTokenType::getTypeFromToken('tcp123'); + } + + public function testGetTypeFromTokenNonExisting(): void + { + $this->expectException(\ValueError::class); + ApiTokenType::getTypeFromToken('abc_123'); + } +} From bcd41c4d9b7a347bbd575c6c3b2a4a0a7d9dbe72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Wed, 16 Aug 2023 00:13:01 +0200 Subject: [PATCH 07/62] Added possibility to authenticate with the given API token --- config/packages/api_platform.yaml | 9 ++- config/packages/security.yaml | 3 + src/ApiPlatform/OpenApiFactoryDecorator.php | 50 +++++++++++++++ .../UserSystem/ApiTokenRepository.php | 5 +- src/Security/ApiTokenHandler.php | 61 +++++++++++++++++++ 5 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 src/ApiPlatform/OpenApiFactoryDecorator.php create mode 100644 src/Security/ApiTokenHandler.php diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml index eb4bc686..d594f3e8 100644 --- a/config/packages/api_platform.yaml +++ b/config/packages/api_platform.yaml @@ -3,4 +3,11 @@ api_platform: title: 'Part-DB API' description: 'API of Part-DB' - version: '0.1.0' \ No newline at end of file + version: '0.1.0' + + swagger: + api_keys: + # overridden in OpenApiFactoryDecorator + access_token: + name: Authorization + type: header \ No newline at end of file diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 92b9f188..116bdf40 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -21,6 +21,9 @@ security: user_checker: App\Security\UserChecker entry_point: form_login + access_token: + token_handler: App\Security\ApiTokenHandler + # Enable user impersonation switch_user: { role: CAN_SWITCH_USER } diff --git a/src/ApiPlatform/OpenApiFactoryDecorator.php b/src/ApiPlatform/OpenApiFactoryDecorator.php new file mode 100644 index 00000000..af213e14 --- /dev/null +++ b/src/ApiPlatform/OpenApiFactoryDecorator.php @@ -0,0 +1,50 @@ +. + */ + +declare(strict_types=1); + + +namespace App\ApiPlatform; + +use ApiPlatform\OpenApi\Factory\OpenApiFactoryInterface; +use ApiPlatform\OpenApi\Model\SecurityScheme; +use ApiPlatform\OpenApi\OpenApi; +use Symfony\Component\DependencyInjection\Attribute\AsDecorator; + +#[AsDecorator('api_platform.openapi.factory')] +class OpenApiFactoryDecorator implements OpenApiFactoryInterface +{ + public function __construct(private readonly OpenApiFactoryInterface $decorated) + { + } + + public function __invoke(array $context = []): OpenApi + { + $openApi = $this->decorated->__invoke($context); + $securitySchemes = $openApi->getComponents()->getSecuritySchemes() ?: new \ArrayObject(); + $securitySchemes['access_token'] = new SecurityScheme( + type: 'http', + description: 'Use an API token to authenticate', + name: 'Authorization', + scheme: 'bearer', + ); + return $openApi; + } +} \ No newline at end of file diff --git a/src/Repository/UserSystem/ApiTokenRepository.php b/src/Repository/UserSystem/ApiTokenRepository.php index 7e11ee53..d0de0f7e 100644 --- a/src/Repository/UserSystem/ApiTokenRepository.php +++ b/src/Repository/UserSystem/ApiTokenRepository.php @@ -23,10 +23,11 @@ declare(strict_types=1); namespace App\Repository\UserSystem; -use App\Repository\NamedDBElementRepository; +use App\Entity\UserSystem\ApiToken; +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\ORM\EntityRepository; +use Doctrine\Persistence\ManagerRegistry; class ApiTokenRepository extends EntityRepository { - } \ No newline at end of file diff --git a/src/Security/ApiTokenHandler.php b/src/Security/ApiTokenHandler.php new file mode 100644 index 00000000..30067c1d --- /dev/null +++ b/src/Security/ApiTokenHandler.php @@ -0,0 +1,61 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Security; + +use App\Entity\UserSystem\ApiToken; +use App\Repository\UserSystem\ApiTokenRepository; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Security\Core\Exception\AccessDeniedException; +use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException; +use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Passport; + +class ApiTokenHandler implements AccessTokenHandlerInterface +{ + private readonly ApiTokenRepository $apiTokenRepository; + + public function __construct(EntityManagerInterface $entityManager) + { + $this->apiTokenRepository = $entityManager->getRepository(ApiToken::class); + } + + public function getUserBadgeFrom(#[\SensitiveParameter] string $accessToken): UserBadge + { + $token = $this->apiTokenRepository->findOneBy(['token' => $accessToken]); + + if (!$token instanceof ApiToken) { + throw new BadCredentialsException(); + } + + if (!$token->isValid()) { + throw new CustomUserMessageAuthenticationException('Token expired'); + } + + return new UserBadge( + userIdentifier: $token->getUser()?->getUserIdentifier() ?? throw new BadCredentialsException(), + ); + } +} \ No newline at end of file From 8dad143f8d74eca886202a96ce0849db3bd17351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Thu, 17 Aug 2023 00:17:02 +0200 Subject: [PATCH 08/62] Added own APIToken authenticator, so we can wrap the used API token inside the symfony security token --- config/packages/security.yaml | 6 +- ...15212203.php => Version20230816213201.php} | 4 +- src/Controller/UserSettingsController.php | 8 +- src/Entity/UserSystem/ApiToken.php | 37 ++++ src/Entity/UserSystem/ApiTokenLevel.php | 58 +++++++ src/Entity/UserSystem/User.php | 22 +++ src/Security/ApiTokenAuthenticatedToken.php | 52 ++++++ src/Security/ApiTokenAuthenticator.php | 159 ++++++++++++++++++ src/Security/ApiTokenBadge.php | 51 ++++++ src/Security/ApiTokenHandler.php | 61 ------- 10 files changed, 391 insertions(+), 67 deletions(-) rename migrations/{Version20230815212203.php => Version20230816213201.php} (70%) create mode 100644 src/Entity/UserSystem/ApiTokenLevel.php create mode 100644 src/Security/ApiTokenAuthenticatedToken.php create mode 100644 src/Security/ApiTokenAuthenticator.php create mode 100644 src/Security/ApiTokenBadge.php delete mode 100644 src/Security/ApiTokenHandler.php diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 116bdf40..b60c632c 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -21,12 +21,12 @@ security: user_checker: App\Security\UserChecker entry_point: form_login - access_token: - token_handler: App\Security\ApiTokenHandler - # Enable user impersonation switch_user: { role: CAN_SWITCH_USER } + custom_authenticators: + - App\Security\ApiTokenAuthenticator + two_factor: auth_form_path: 2fa_login check_path: 2fa_login_check diff --git a/migrations/Version20230815212203.php b/migrations/Version20230816213201.php similarity index 70% rename from migrations/Version20230815212203.php rename to migrations/Version20230816213201.php index 1a6c372e..d87a676c 100644 --- a/migrations/Version20230815212203.php +++ b/migrations/Version20230816213201.php @@ -10,7 +10,7 @@ use Doctrine\Migrations\AbstractMigration; /** * Auto-generated Migration: Please modify to your needs! */ -final class Version20230815212203 extends AbstractMigration +final class Version20230816213201 extends AbstractMigration { public function getDescription(): string { @@ -20,7 +20,7 @@ final class Version20230815212203 extends AbstractMigration public function up(Schema $schema): void { // this up() migration is auto-generated, please modify it to your needs - $this->addSql('CREATE TABLE api_tokens (id INT AUTO_INCREMENT NOT NULL, user_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, valid_until DATETIME DEFAULT NULL, token VARCHAR(68) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, UNIQUE INDEX UNIQ_2CAD560E5F37A13B (token), INDEX IDX_2CAD560EA76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE api_tokens (id INT AUTO_INCREMENT NOT NULL, user_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, valid_until DATETIME DEFAULT NULL, token VARCHAR(68) NOT NULL, level SMALLINT NOT NULL, last_time_used DATETIME DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, UNIQUE INDEX UNIQ_2CAD560E5F37A13B (token), INDEX IDX_2CAD560EA76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); $this->addSql('ALTER TABLE api_tokens ADD CONSTRAINT FK_2CAD560EA76ED395 FOREIGN KEY (user_id) REFERENCES `users` (id)'); } diff --git a/src/Controller/UserSettingsController.php b/src/Controller/UserSettingsController.php index 1bc9318e..acd18206 100644 --- a/src/Controller/UserSettingsController.php +++ b/src/Controller/UserSettingsController.php @@ -24,6 +24,7 @@ namespace App\Controller; use App\Entity\Attachments\Attachment; use App\Entity\UserSystem\ApiToken; +use App\Entity\UserSystem\ApiTokenLevel; use App\Entity\UserSystem\U2FKey; use App\Entity\UserSystem\User; use App\Entity\UserSystem\WebauthnKey; @@ -41,6 +42,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Form\Extension\Core\Type\DateTimeType; +use Symfony\Component\Form\Extension\Core\Type\EnumType; use Symfony\Component\Form\Extension\Core\Type\PasswordType; use Symfony\Component\Form\Extension\Core\Type\RepeatedType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; @@ -405,6 +407,7 @@ class UserSettingsController extends AbstractController public function addApiToken(Request $request, EntityManagerInterface $entityManager): Response { $token = new ApiToken(); + $token->setUser($this->getUser()); $secret = null; @@ -418,6 +421,10 @@ class UserSettingsController extends AbstractController 'required' => false, 'html5' => true ]) + ->add('level', EnumType::class, [ + 'class' => ApiTokenLevel::class, + 'label' => 'user.api_token.level', + ]) ->add('submit', SubmitType::class, [ 'label' => 'save', ]) @@ -426,7 +433,6 @@ class UserSettingsController extends AbstractController $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - $token->setUser($this->getUser()); $entityManager->persist($token); $entityManager->flush(); diff --git a/src/Entity/UserSystem/ApiToken.php b/src/Entity/UserSystem/ApiToken.php index f895febf..0002fbaf 100644 --- a/src/Entity/UserSystem/ApiToken.php +++ b/src/Entity/UserSystem/ApiToken.php @@ -58,6 +58,12 @@ class ApiToken #[ORM\Column(length: 68, unique: true)] private string $token; + #[ORM\Column(type: Types::SMALLINT, enumType: ApiTokenLevel::class)] + private ApiTokenLevel $level = ApiTokenLevel::READ_ONLY; + + #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)] + private ?\DateTimeInterface $last_time_used = null; + public function __construct(ApiTokenType $tokenType = ApiTokenType::PERSONAL_ACCESS_TOKEN) { // Generate a rondom token on creation. The tokenType is 3 characters long (plus underscore), so the token is 68 characters long. @@ -121,5 +127,36 @@ class ApiToken return $this; } + /** + * Gets the last time the token was used to authenticate or null if it was never used. + * @return \DateTimeInterface|null + */ + public function getLastTimeUsed(): ?\DateTimeInterface + { + return $this->last_time_used; + } + + /** + * Sets the last time the token was used to authenticate. + * @param \DateTimeInterface|null $last_time_used + * @return ApiToken + */ + public function setLastTimeUsed(?\DateTimeInterface $last_time_used): ApiToken + { + $this->last_time_used = $last_time_used; + return $this; + } + + public function getLevel(): ApiTokenLevel + { + return $this->level; + } + + public function setLevel(ApiTokenLevel $level): ApiToken + { + $this->level = $level; + return $this; + } + } \ No newline at end of file diff --git a/src/Entity/UserSystem/ApiTokenLevel.php b/src/Entity/UserSystem/ApiTokenLevel.php new file mode 100644 index 00000000..ee2f6bb5 --- /dev/null +++ b/src/Entity/UserSystem/ApiTokenLevel.php @@ -0,0 +1,58 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Entity\UserSystem; + +enum ApiTokenLevel: int +{ + private const ROLE_READ_ONLY = 'ROLE_API_READ_ONLY'; + private const ROLE_EDIT = 'ROLE_API_EDIT'; + private const ROLE_FULL = 'ROLE_API_FULL'; + + /** + * The token can only read (non-sensitive) data. + */ + case READ_ONLY = 1; + /** + * The token can read and edit (non-sensitive) data. + */ + case EDIT = 2; + /** + * The token can do everything the user can do. + */ + case FULL = 3; + + /** + * Returns the additional roles that the authenticated user should have when using this token. + * @return string[] + */ + public function getAdditionalRoles(): array + { + //The higher roles should always include the lower ones + return match ($this) { + self::READ_ONLY => [self::ROLE_READ_ONLY], + self::EDIT => [self::ROLE_READ_ONLY, self::ROLE_EDIT], + self::FULL => [self::ROLE_READ_ONLY, self::ROLE_EDIT, self::ROLE_FULL], + }; + } +} \ No newline at end of file diff --git a/src/Entity/UserSystem/User.php b/src/Entity/UserSystem/User.php index cfc144d8..e2a3ff8b 100644 --- a/src/Entity/UserSystem/User.php +++ b/src/Entity/UserSystem/User.php @@ -315,6 +315,11 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe #[ORM\Column(type: Types::BOOLEAN)] protected bool $saml_user = false; + /** + * @var ApiToken|null The api token which is used to authenticate the user, or null if the user is not authenticated via api token + */ + private ?ApiToken $authenticating_api_token = null; + public function __construct() { $this->attachments = new ArrayCollection(); @@ -1035,6 +1040,23 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe $this->api_tokens->removeElement($apiToken); } + /** + * Mark the user as authenticated with an API token, should only be used by the API token authenticator. + * @param ApiToken $apiToken + * @return void + */ + public function markAsApiTokenAuthenticated(ApiToken $apiToken): void + { + $this->authenticating_api_token = $apiToken; + } + /** + * Return the API token that is currently authenticating the user or null if the user is not authenticated with an API token. + * @return ApiToken|null + */ + public function getAuthenticatingApiToken(): ?ApiToken + { + return $this->authenticating_api_token; + } } diff --git a/src/Security/ApiTokenAuthenticatedToken.php b/src/Security/ApiTokenAuthenticatedToken.php new file mode 100644 index 00000000..2f186e63 --- /dev/null +++ b/src/Security/ApiTokenAuthenticatedToken.php @@ -0,0 +1,52 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Security; + +use App\Entity\UserSystem\ApiToken; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken; + +class ApiTokenAuthenticatedToken extends PostAuthenticationToken +{ + public function __construct(UserInterface $user, string $firewallName, array $roles, private readonly ApiToken $apiToken) + { + //Add roles for the API + $roles[] = 'ROLE_API_AUTHENTICATED'; + + //Add roles based on the token level + $roles = array_merge($roles, $apiToken->getLevel()->getAdditionalRoles()); + + + parent::__construct($user, $firewallName, array_unique($roles)); + } + + /** + * Returns the API token that was used to authenticate the user. + * @return ApiToken + */ + public function getApiToken(): ApiToken + { + return $this->apiToken; + } +} \ No newline at end of file diff --git a/src/Security/ApiTokenAuthenticator.php b/src/Security/ApiTokenAuthenticator.php new file mode 100644 index 00000000..6d3dc09c --- /dev/null +++ b/src/Security/ApiTokenAuthenticator.php @@ -0,0 +1,159 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Security; + +use App\Entity\UserSystem\ApiToken; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException; +use Symfony\Component\Security\Http\AccessToken\AccessTokenExtractorInterface; +use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Passport; +use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * Authenticator similar to the builtin AccessTokenAuthenticator, but we return a Token here which contains information + * about the used token. + */ +class ApiTokenAuthenticator implements AuthenticatorInterface +{ + public function __construct( + #[Autowire(service: 'security.access_token_extractor.header')] + private readonly AccessTokenExtractorInterface $accessTokenExtractor, + private readonly TranslatorInterface $translator, + private readonly EntityManagerInterface $entityManager, + private readonly string $realm = 'api', + ) { + } + + /** + * Gets the ApiToken belonging to the given accessToken string. + * If the token is invalid or expired, an exception is thrown and authentication fails. + * @param string $accessToken + * @return ApiToken + */ + private function getTokenFromString(#[\SensitiveParameter] string $accessToken): ApiToken + { + $repo = $this->entityManager->getRepository(ApiToken::class); + $token = $repo->findOneBy(['token' => $accessToken]); + + if (!$token instanceof ApiToken) { + throw new BadCredentialsException(); + } + + if (!$token->isValid()) { + throw new CustomUserMessageAuthenticationException('Token expired'); + } + + $old_time = $token->getLastTimeUsed(); + //Set the last used date of the token + $token->setLastTimeUsed(new \DateTimeImmutable()); + //Only flush the token if the last used date change is more than 10 minutes + //For performance reasons we don't want to flush the token every time it is used, but only if it is used more than 10 minutes after the last time it was used + //If a flush is later in the code we don't want to flush the token again + if ($old_time === null || $old_time->diff($token->getLastTimeUsed())->i > 10) { + $this->entityManager->flush(); + } + + return $token; + } + + public function supports(Request $request): ?bool + { + return null === $this->accessTokenExtractor->extractAccessToken($request) ? false : null; + } + + public function authenticate(Request $request): Passport + { + $accessToken = $this->accessTokenExtractor->extractAccessToken($request); + if (!$accessToken) { + throw new BadCredentialsException('Invalid credentials.'); + } + + $apiToken = $this->getTokenFromString($accessToken); + $userBadge = new UserBadge($apiToken->getUser()?->getUserIdentifier() ?? throw new BadCredentialsException('Invalid credentials.')); + $apiBadge = new ApiTokenBadge($apiToken); + + return new SelfValidatingPassport($userBadge, [$apiBadge]); + } + + public function createToken(Passport $passport, string $firewallName): TokenInterface + { + return new ApiTokenAuthenticatedToken( + $passport->getUser(), + $firewallName, + $passport->getUser()->getRoles(), + $passport->getBadge(ApiTokenBadge::class)?->getApiToken() ?? throw new \LogicException('Passport does not contain an API token.') + ); + } + + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response + { + if (null !== $this->translator) { + $errorMessage = $this->translator->trans($exception->getMessageKey(), $exception->getMessageData(), + 'security'); + } else { + $errorMessage = strtr($exception->getMessageKey(), $exception->getMessageData()); + } + + return new Response( + null, + Response::HTTP_UNAUTHORIZED, + ['WWW-Authenticate' => $this->getAuthenticateHeader($errorMessage)] + ); + } + + /** + * @see https://datatracker.ietf.org/doc/html/rfc6750#section-3 + */ + private function getAuthenticateHeader(string $errorDescription = null): string + { + $data = [ + 'realm' => $this->realm, + 'error' => 'invalid_token', + 'error_description' => $errorDescription, + ]; + $values = []; + foreach ($data as $k => $v) { + if (null === $v || '' === $v) { + continue; + } + $values[] = sprintf('%s="%s"', $k, $v); + } + + return sprintf('Bearer %s', implode(',', $values)); + } +public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response +{ + return null; +} +} \ No newline at end of file diff --git a/src/Security/ApiTokenBadge.php b/src/Security/ApiTokenBadge.php new file mode 100644 index 00000000..d2429a06 --- /dev/null +++ b/src/Security/ApiTokenBadge.php @@ -0,0 +1,51 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Security; + +use App\Entity\UserSystem\ApiToken; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface; + +class ApiTokenBadge implements BadgeInterface +{ + + /** + * @param ApiToken $apiToken + */ + public function __construct(private readonly ApiToken $apiToken) + { + } + + /** + * @return ApiToken The token that was used to authenticate the user + */ + public function getApiToken(): ApiToken + { + return $this->apiToken; + } + + public function isResolved(): bool + { + return true; + } +} \ No newline at end of file diff --git a/src/Security/ApiTokenHandler.php b/src/Security/ApiTokenHandler.php deleted file mode 100644 index 30067c1d..00000000 --- a/src/Security/ApiTokenHandler.php +++ /dev/null @@ -1,61 +0,0 @@ -. - */ - -declare(strict_types=1); - - -namespace App\Security; - -use App\Entity\UserSystem\ApiToken; -use App\Repository\UserSystem\ApiTokenRepository; -use Doctrine\ORM\EntityManagerInterface; -use Symfony\Component\Security\Core\Exception\AccessDeniedException; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; -use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException; -use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface; -use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; -use Symfony\Component\Security\Http\Authenticator\Passport\Passport; - -class ApiTokenHandler implements AccessTokenHandlerInterface -{ - private readonly ApiTokenRepository $apiTokenRepository; - - public function __construct(EntityManagerInterface $entityManager) - { - $this->apiTokenRepository = $entityManager->getRepository(ApiToken::class); - } - - public function getUserBadgeFrom(#[\SensitiveParameter] string $accessToken): UserBadge - { - $token = $this->apiTokenRepository->findOneBy(['token' => $accessToken]); - - if (!$token instanceof ApiToken) { - throw new BadCredentialsException(); - } - - if (!$token->isValid()) { - throw new CustomUserMessageAuthenticationException('Token expired'); - } - - return new UserBadge( - userIdentifier: $token->getUser()?->getUserIdentifier() ?? throw new BadCredentialsException(), - ); - } -} \ No newline at end of file From 040e86ea6d6398cd32450db93b0b01c1340bb36e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Thu, 17 Aug 2023 00:36:25 +0200 Subject: [PATCH 09/62] Added API endpoint to get info about the API token currently used to authenticate the user --- src/Entity/UserSystem/ApiToken.php | 21 +++++++++++ src/State/CurrentApiTokenProvider.php | 51 +++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 src/State/CurrentApiTokenProvider.php diff --git a/src/Entity/UserSystem/ApiToken.php b/src/Entity/UserSystem/ApiToken.php index 0002fbaf..03b87a7a 100644 --- a/src/Entity/UserSystem/ApiToken.php +++ b/src/Entity/UserSystem/ApiToken.php @@ -23,18 +23,34 @@ declare(strict_types=1); namespace App\Entity\UserSystem; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Serializer\Filter\PropertyFilter; use App\Entity\Base\AbstractNamedDBElement; use App\Entity\Base\TimestampTrait; use App\Repository\UserSystem\ApiTokenRepository; +use App\State\CurrentApiTokenProvider; +use App\State\PartDBInfoProvider; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; +use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints\NotBlank; #[ORM\Entity(repositoryClass: ApiTokenRepository::class)] #[ORM\Table(name: 'api_tokens')] #[ORM\HasLifecycleCallbacks] #[UniqueEntity(fields: ['name', 'user'])] + +#[ApiResource( + uriTemplate: '/current.{_format}', + description: 'A token used to authenticate API requests.', + operations: [new Get(openapiContext: ['summary' => 'Get information about the API token that is currently used.'])], + normalizationContext: ['groups' => ['token:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + provider: CurrentApiTokenProvider::class, +)] +#[ApiFilter(PropertyFilter::class)] class ApiToken { @@ -47,21 +63,26 @@ class ApiToken #[ORM\Column(type: Types::STRING)] #[NotBlank] + #[Groups('token:read')] protected string $name = ''; #[ORM\ManyToOne(inversedBy: 'api_tokens')] + #[Groups('token:read')] private ?User $user = null; #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)] + #[Groups('token:read')] private ?\DateTimeInterface $valid_until = null; #[ORM\Column(length: 68, unique: true)] private string $token; #[ORM\Column(type: Types::SMALLINT, enumType: ApiTokenLevel::class)] + #[Groups('token:read')] private ApiTokenLevel $level = ApiTokenLevel::READ_ONLY; #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)] + #[Groups('token:read')] private ?\DateTimeInterface $last_time_used = null; public function __construct(ApiTokenType $tokenType = ApiTokenType::PERSONAL_ACCESS_TOKEN) diff --git a/src/State/CurrentApiTokenProvider.php b/src/State/CurrentApiTokenProvider.php new file mode 100644 index 00000000..7011f4f9 --- /dev/null +++ b/src/State/CurrentApiTokenProvider.php @@ -0,0 +1,51 @@ +. + */ + +declare(strict_types=1); + + +namespace App\State; + +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\Operation; +use ApiPlatform\Serializer\Filter\PropertyFilter; +use ApiPlatform\State\ProviderInterface; +use App\Security\ApiTokenAuthenticatedToken; +use Symfony\Bundle\SecurityBundle\Security; + + +class CurrentApiTokenProvider implements ProviderInterface +{ + + public function __construct(private readonly Security $security) + { + + } + + public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null + { + $securityToken = $this->security->getToken(); + if (!$securityToken instanceof ApiTokenAuthenticatedToken) { + return null; + } + + return $securityToken->getApiToken(); + } +} \ No newline at end of file From 35a0e8464a655acee9f6f4ca23aea0ba87673f8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sat, 19 Aug 2023 23:19:21 +0200 Subject: [PATCH 10/62] Added possibility to list all available API keys at the user settings page --- assets/js/register_events.js | 6 + src/Controller/UserSettingsController.php | 19 ++-- src/Entity/UserSystem/ApiToken.php | 5 +- src/Entity/UserSystem/ApiTokenLevel.php | 15 ++- src/Entity/UserSystem/User.php | 27 +---- templates/users/_api_tokens.html.twig | 61 ++++++++++ templates/users/api_token_create.html.twig | 26 ++++- templates/users/user_settings.html.twig | 2 + translations/messages.en.xlf | 126 +++++++++++++++++++++ 9 files changed, 245 insertions(+), 42 deletions(-) create mode 100644 templates/users/_api_tokens.html.twig diff --git a/assets/js/register_events.js b/assets/js/register_events.js index 22e91fdf..5d2aece0 100644 --- a/assets/js/register_events.js +++ b/assets/js/register_events.js @@ -20,6 +20,7 @@ 'use strict'; import {Dropdown} from "bootstrap"; +import ClipboardJS from "clipboard"; class RegisterEventHelper { constructor() { @@ -27,6 +28,11 @@ class RegisterEventHelper { this.configureDropdowns(); this.registerSpecialCharInput(); + //Initialize ClipboardJS + this.registerLoadHandler(() => { + new ClipboardJS('.btn'); + }) + this.registerModalDropRemovalOnFormSubmit(); } diff --git a/src/Controller/UserSettingsController.php b/src/Controller/UserSettingsController.php index acd18206..3f9d89bd 100644 --- a/src/Controller/UserSettingsController.php +++ b/src/Controller/UserSettingsController.php @@ -413,17 +413,20 @@ class UserSettingsController extends AbstractController $form = $this->createFormBuilder($token) ->add('name', TextType::class, [ - 'label' => 'user.api_token.name', - ]) - ->add('valid_until', DateTimeType::class, [ - 'label' => 'user.api_token.valid_until', - 'widget' => 'single_text', - 'required' => false, - 'html5' => true + 'label' => 'api_tokens.name', ]) ->add('level', EnumType::class, [ 'class' => ApiTokenLevel::class, - 'label' => 'user.api_token.level', + 'label' => 'api_tokens.access_level', + 'help' => 'api_tokens.access_level.help', + 'choice_label' => fn (ApiTokenLevel $level) => $level->getTranslationKey(), + ]) + ->add('valid_until', DateTimeType::class, [ + 'label' => 'api_tokens.expiration_date', + 'widget' => 'single_text', + 'help' => 'api_tokens.expiration_date.help', + 'required' => false, + 'html5' => true ]) ->add('submit', SubmitType::class, [ 'label' => 'save', diff --git a/src/Entity/UserSystem/ApiToken.php b/src/Entity/UserSystem/ApiToken.php index 03b87a7a..e344e464 100644 --- a/src/Entity/UserSystem/ApiToken.php +++ b/src/Entity/UserSystem/ApiToken.php @@ -72,7 +72,7 @@ class ApiToken #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)] #[Groups('token:read')] - private ?\DateTimeInterface $valid_until = null; + private ?\DateTimeInterface $valid_until; #[ORM\Column(length: 68, unique: true)] private string $token; @@ -89,6 +89,9 @@ class ApiToken { // Generate a rondom token on creation. The tokenType is 3 characters long (plus underscore), so the token is 68 characters long. $this->token = $tokenType->getTokenPrefix() . bin2hex(random_bytes(32)); + + //By default, tokens are valid for 1 year. + $this->valid_until = new \DateTime('+1 year'); } public function getTokenType(): ApiTokenType diff --git a/src/Entity/UserSystem/ApiTokenLevel.php b/src/Entity/UserSystem/ApiTokenLevel.php index ee2f6bb5..8289e57c 100644 --- a/src/Entity/UserSystem/ApiTokenLevel.php +++ b/src/Entity/UserSystem/ApiTokenLevel.php @@ -37,10 +37,14 @@ enum ApiTokenLevel: int * The token can read and edit (non-sensitive) data. */ case EDIT = 2; + /** + * The token can do some administrative tasks (like viewing all log entries), but can not change passwords and create new tokens. + */ + case ADMIN = 3; /** * The token can do everything the user can do. */ - case FULL = 3; + case FULL = 4; /** * Returns the additional roles that the authenticated user should have when using this token. @@ -55,4 +59,13 @@ enum ApiTokenLevel: int self::FULL => [self::ROLE_READ_ONLY, self::ROLE_EDIT, self::ROLE_FULL], }; } + + /** + * Returns the translation key for the name of this token level. + * @return string + */ + public function getTranslationKey(): string + { + return 'api_token.level.' . strtolower($this->name); + } } \ No newline at end of file diff --git a/src/Entity/UserSystem/User.php b/src/Entity/UserSystem/User.php index e2a3ff8b..ae4f90d0 100644 --- a/src/Entity/UserSystem/User.php +++ b/src/Entity/UserSystem/User.php @@ -315,11 +315,6 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe #[ORM\Column(type: Types::BOOLEAN)] protected bool $saml_user = false; - /** - * @var ApiToken|null The api token which is used to authenticate the user, or null if the user is not authenticated via api token - */ - private ?ApiToken $authenticating_api_token = null; - public function __construct() { $this->attachments = new ArrayCollection(); @@ -1013,7 +1008,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe /** * Return all API tokens of the user. - * @return Collection + * @return Collection */ public function getApiTokens(): Collection { @@ -1039,24 +1034,4 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe { $this->api_tokens->removeElement($apiToken); } - - /** - * Mark the user as authenticated with an API token, should only be used by the API token authenticator. - * @param ApiToken $apiToken - * @return void - */ - public function markAsApiTokenAuthenticated(ApiToken $apiToken): void - { - $this->authenticating_api_token = $apiToken; - } - - /** - * Return the API token that is currently authenticating the user or null if the user is not authenticated with an API token. - * @return ApiToken|null - */ - public function getAuthenticatingApiToken(): ?ApiToken - { - return $this->authenticating_api_token; - } - } diff --git a/templates/users/_api_tokens.html.twig b/templates/users/_api_tokens.html.twig new file mode 100644 index 00000000..d52b8970 --- /dev/null +++ b/templates/users/_api_tokens.html.twig @@ -0,0 +1,61 @@ +{# @var user \App\Entity\UserSystem\User #} + +{% macro format_date(datetime) %} + {% if datetime is null %} + {% trans %}datetime.never{% endtrans %} + {% else %} + {{ datetime|format_datetime }} + {% endif %} +{% endmacro %} + +
+
+ + {% trans %}user.settings.api_tokens{% endtrans %} +
+
+ {% trans %}user.settings.api_tokens.description{% endtrans %}
+ {% trans %}user.settings.show_api_documentation{% endtrans %} + + {% if user.apiTokens.empty %} +

+ {% trans %}user.settings.api_tokens.no_api_tokens_yet{% endtrans %} + {% else %} + + + + + + + + + + + + + + {% for api_token in user.apiTokens %} + {# @var api_token \App\Entity\UserSystem\ApiToken #} + + + + + + + + {% endfor %} + +
{% trans %}api_tokens.name{% endtrans %}{% trans %}api_tokens.access_level{% endtrans %}{% trans %}api_tokens.expiration_date{% endtrans %}{% trans %}tfa_u2f.keys.added_date{% endtrans %}{% trans %}api_tokens.last_time_used{% endtrans %}
{{ api_token.name }}{{ api_token.level.translationKey|trans }} + {{ _self.format_date(api_token.validUntil) }} + {% if api_token.valid %} + {% trans %}api_token.valid{% endtrans %} + {% else %} + {% trans %}api_token.expired{% endtrans %} + {% endif %} + {{ _self.format_date(api_token.addedDate) }}{{ _self.format_date(api_token.lastTimeUsed) }}
+ {% endif %} + + {% trans %}api_token.create_new{% endtrans %} + +
+
\ No newline at end of file diff --git a/templates/users/api_token_create.html.twig b/templates/users/api_token_create.html.twig index 57b86708..1a159e73 100644 --- a/templates/users/api_token_create.html.twig +++ b/templates/users/api_token_create.html.twig @@ -1,16 +1,30 @@ {% extends "main_card.html.twig" %} -{% block card_title %}Add API token{% endblock %} +{% block title %}{% trans %}api_token.create_new{% endtrans %}{% endblock %} + +{% block card_title %} + + {% trans %}api_token.create_new{% endtrans %} +{% endblock %} {% block card_content %} {# Show API secret after submit #} {% if secret is not null %}
- Your API token is: {{ secret }}
- Please save it. You wont be able to see it again! -
- {% endif %} + {% trans %}api_tokens.your_token_is{% endtrans %}:
+
+ + +
+ {% trans %}api_tokens.please_save_it{% endtrans %} +
- {{ form(form) }} + {% trans %}api_tokens.create_new.back_to_user_settings{% endtrans %} + + {% else %} + {{ form(form) }} + {% endif %} {% endblock %} \ No newline at end of file diff --git a/templates/users/user_settings.html.twig b/templates/users/user_settings.html.twig index 650ebe97..359b993e 100644 --- a/templates/users/user_settings.html.twig +++ b/templates/users/user_settings.html.twig @@ -76,4 +76,6 @@ {{ form_end(pw_form) }} + + {% include "users/_api_tokens.html.twig" %} {% endblock %} diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index 92e04ef6..7771601e 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -11603,5 +11603,131 @@ Please note, that you can not impersonate a disabled user. If you try you will g Show available Part-DB updates + + + user.settings.api_tokens + API tokens + + + + + user.settings.api_tokens.description + Using an API token, other applications can access Part-DB with your user rights, to perform various actions using the Part-DB REST API. If you delete an API token here, the application, which uses the token, will no longer be able to access Part-DB on your behalf. + + + + + api_tokens.name + Name + + + + + api_tokens.access_level + Access level + + + + + api_tokens.expiration_date + Expiration date + + + + + api_tokens.added_date + api_tokens.added_date + + + + + api_tokens.last_time_used + Last time used + + + + + datetime.never + Never + + + + + api_token.valid + Valid + + + + + api_token.expired + Expired + + + + + user.settings.show_api_documentation + Show API documentation + + + + + api_token.create_new + Create new API token + + + + + api_token.level.read_only + Read-Only + + + + + api_token.level.edit + Edit + + + + + api_token.level.admin + Admin + + + + + api_token.level.full + Full + + + + + api_tokens.access_level.help + You can restrict, what the API token can access. The access is always limited by the permissions of your user. + + + + + api_tokens.expiration_date.help + After this date, the token is not usable anymore. Leave empty if the token should never expire. + + + + + api_tokens.your_token_is + Your API token is + + + + + api_tokens.please_save_it + Please save it. You will not be able to see it again! + + + + + api_tokens.create_new.back_to_user_settings + Back to user settings + + From be14fe548c497a2950f204e970a5f94a78502928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sat, 26 Aug 2023 22:16:49 +0200 Subject: [PATCH 11/62] Updated composer dependencies. --- composer.lock | 280 +++++++++++++++++++++++++------------------------- 1 file changed, 140 insertions(+), 140 deletions(-) diff --git a/composer.lock b/composer.lock index d8c07dab..bc340258 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "33a669a1ec6b3bb4a92afc79355492fc", + "content-hash": "cab75954c0c9b5570e36456a8a2bc9ed", "packages": [ { "name": "api-platform/core", - "version": "v3.1.13", + "version": "v3.1.15", "source": { "type": "git", "url": "https://github.com/api-platform/core.git", - "reference": "190bb4eabeafbe8e830af4a8ccac1feaf5b74e96" + "reference": "0b51d280a101e9f8bd9cfe9c18968164d8e19375" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/core/zipball/190bb4eabeafbe8e830af4a8ccac1feaf5b74e96", - "reference": "190bb4eabeafbe8e830af4a8ccac1feaf5b74e96", + "url": "https://api.github.com/repos/api-platform/core/zipball/0b51d280a101e9f8bd9cfe9c18968164d8e19375", + "reference": "0b51d280a101e9f8bd9cfe9c18968164d8e19375", "shasum": "" }, "require": { @@ -165,7 +165,7 @@ ], "support": { "issues": "https://github.com/api-platform/core/issues", - "source": "https://github.com/api-platform/core/tree/v3.1.13" + "source": "https://github.com/api-platform/core/tree/v3.1.15" }, "funding": [ { @@ -173,7 +173,7 @@ "type": "tidelift" } ], - "time": "2023-08-03T16:44:18+00:00" + "time": "2023-08-25T08:54:22+00:00" }, { "name": "beberlei/assert", @@ -7360,16 +7360,16 @@ }, { "name": "symfony/cache", - "version": "v6.3.2", + "version": "v6.3.4", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "d176b97600860df13e851538c2df2ad88e9e5ca9" + "reference": "e60d00b4f633efa4c1ef54e77c12762d9073e7b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/d176b97600860df13e851538c2df2ad88e9e5ca9", - "reference": "d176b97600860df13e851538c2df2ad88e9e5ca9", + "url": "https://api.github.com/repos/symfony/cache/zipball/e60d00b4f633efa4c1ef54e77c12762d9073e7b3", + "reference": "e60d00b4f633efa4c1ef54e77c12762d9073e7b3", "shasum": "" }, "require": { @@ -7436,7 +7436,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v6.3.2" + "source": "https://github.com/symfony/cache/tree/v6.3.4" }, "funding": [ { @@ -7452,7 +7452,7 @@ "type": "tidelift" } ], - "time": "2023-07-27T16:19:14+00:00" + "time": "2023-08-05T09:10:27+00:00" }, { "name": "symfony/cache-contracts", @@ -7532,16 +7532,16 @@ }, { "name": "symfony/clock", - "version": "v6.3.1", + "version": "v6.3.4", "source": { "type": "git", "url": "https://github.com/symfony/clock.git", - "reference": "2c72817f85cbdd0ae4e49643514a889004934296" + "reference": "a74086d3db70d0f06ffd84480daa556248706e98" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/clock/zipball/2c72817f85cbdd0ae4e49643514a889004934296", - "reference": "2c72817f85cbdd0ae4e49643514a889004934296", + "url": "https://api.github.com/repos/symfony/clock/zipball/a74086d3db70d0f06ffd84480daa556248706e98", + "reference": "a74086d3db70d0f06ffd84480daa556248706e98", "shasum": "" }, "require": { @@ -7585,7 +7585,7 @@ "time" ], "support": { - "source": "https://github.com/symfony/clock/tree/v6.3.1" + "source": "https://github.com/symfony/clock/tree/v6.3.4" }, "funding": [ { @@ -7601,7 +7601,7 @@ "type": "tidelift" } ], - "time": "2023-06-08T23:46:55+00:00" + "time": "2023-07-31T11:35:03+00:00" }, { "name": "symfony/config", @@ -7680,16 +7680,16 @@ }, { "name": "symfony/console", - "version": "v6.3.2", + "version": "v6.3.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "aa5d64ad3f63f2e48964fc81ee45cb318a723898" + "reference": "eca495f2ee845130855ddf1cf18460c38966c8b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/aa5d64ad3f63f2e48964fc81ee45cb318a723898", - "reference": "aa5d64ad3f63f2e48964fc81ee45cb318a723898", + "url": "https://api.github.com/repos/symfony/console/zipball/eca495f2ee845130855ddf1cf18460c38966c8b6", + "reference": "eca495f2ee845130855ddf1cf18460c38966c8b6", "shasum": "" }, "require": { @@ -7750,7 +7750,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.3.2" + "source": "https://github.com/symfony/console/tree/v6.3.4" }, "funding": [ { @@ -7766,7 +7766,7 @@ "type": "tidelift" } ], - "time": "2023-07-19T20:17:28+00:00" + "time": "2023-08-16T10:10:12+00:00" }, { "name": "symfony/css-selector", @@ -7835,16 +7835,16 @@ }, { "name": "symfony/dependency-injection", - "version": "v6.3.2", + "version": "v6.3.4", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "474cfbc46aba85a1ca11a27db684480d0db64ba7" + "reference": "68a5a9570806a087982f383f6109c5e925892a49" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/474cfbc46aba85a1ca11a27db684480d0db64ba7", - "reference": "474cfbc46aba85a1ca11a27db684480d0db64ba7", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/68a5a9570806a087982f383f6109c5e925892a49", + "reference": "68a5a9570806a087982f383f6109c5e925892a49", "shasum": "" }, "require": { @@ -7896,7 +7896,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/v6.3.2" + "source": "https://github.com/symfony/dependency-injection/tree/v6.3.4" }, "funding": [ { @@ -7912,7 +7912,7 @@ "type": "tidelift" } ], - "time": "2023-07-19T20:17:28+00:00" + "time": "2023-08-16T17:55:17+00:00" }, { "name": "symfony/deprecation-contracts", @@ -7983,16 +7983,16 @@ }, { "name": "symfony/doctrine-bridge", - "version": "v6.3.2", + "version": "v6.3.4", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-bridge.git", - "reference": "61c7d16fbb61ffe3a08d1b965355d6b1006c3594" + "reference": "589eeeb93669739ec1d8bd4593e4972d94e0981d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/61c7d16fbb61ffe3a08d1b965355d6b1006c3594", - "reference": "61c7d16fbb61ffe3a08d1b965355d6b1006c3594", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/589eeeb93669739ec1d8bd4593e4972d94e0981d", + "reference": "589eeeb93669739ec1d8bd4593e4972d94e0981d", "shasum": "" }, "require": { @@ -8073,7 +8073,7 @@ "description": "Provides integration for Doctrine with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/doctrine-bridge/tree/v6.3.2" + "source": "https://github.com/symfony/doctrine-bridge/tree/v6.3.4" }, "funding": [ { @@ -8089,7 +8089,7 @@ "type": "tidelift" } ], - "time": "2023-07-20T14:51:28+00:00" + "time": "2023-08-08T10:40:25+00:00" }, { "name": "symfony/dotenv", @@ -8750,16 +8750,16 @@ }, { "name": "symfony/framework-bundle", - "version": "v6.3.2", + "version": "v6.3.4", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "930fe7ee25a928b9b3627d717873ddd171430a82" + "reference": "f822f54ff05cd88878910b4559f66c12176d952c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/930fe7ee25a928b9b3627d717873ddd171430a82", - "reference": "930fe7ee25a928b9b3627d717873ddd171430a82", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/f822f54ff05cd88878910b4559f66c12176d952c", + "reference": "f822f54ff05cd88878910b4559f66c12176d952c", "shasum": "" }, "require": { @@ -8874,7 +8874,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/v6.3.2" + "source": "https://github.com/symfony/framework-bundle/tree/v6.3.4" }, "funding": [ { @@ -8890,7 +8890,7 @@ "type": "tidelift" } ], - "time": "2023-07-26T17:39:03+00:00" + "time": "2023-08-16T18:04:38+00:00" }, { "name": "symfony/http-client", @@ -9064,16 +9064,16 @@ }, { "name": "symfony/http-foundation", - "version": "v6.3.2", + "version": "v6.3.4", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "43ed99d30f5f466ffa00bdac3f5f7aa9cd7617c3" + "reference": "cac1556fdfdf6719668181974104e6fcfa60e844" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/43ed99d30f5f466ffa00bdac3f5f7aa9cd7617c3", - "reference": "43ed99d30f5f466ffa00bdac3f5f7aa9cd7617c3", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/cac1556fdfdf6719668181974104e6fcfa60e844", + "reference": "cac1556fdfdf6719668181974104e6fcfa60e844", "shasum": "" }, "require": { @@ -9121,7 +9121,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.3.2" + "source": "https://github.com/symfony/http-foundation/tree/v6.3.4" }, "funding": [ { @@ -9137,20 +9137,20 @@ "type": "tidelift" } ], - "time": "2023-07-23T21:58:39+00:00" + "time": "2023-08-22T08:20:46+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.3.3", + "version": "v6.3.4", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "d3b567f0addf695e10b0c6d57564a9bea2e058ee" + "reference": "36abb425b4af863ae1fe54d8a8b8b4c76a2bccdb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/d3b567f0addf695e10b0c6d57564a9bea2e058ee", - "reference": "d3b567f0addf695e10b0c6d57564a9bea2e058ee", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/36abb425b4af863ae1fe54d8a8b8b4c76a2bccdb", + "reference": "36abb425b4af863ae1fe54d8a8b8b4c76a2bccdb", "shasum": "" }, "require": { @@ -9159,7 +9159,7 @@ "symfony/deprecation-contracts": "^2.5|^3", "symfony/error-handler": "^6.3", "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/http-foundation": "^6.2.7", + "symfony/http-foundation": "^6.3.4", "symfony/polyfill-ctype": "^1.8" }, "conflict": { @@ -9167,7 +9167,7 @@ "symfony/cache": "<5.4", "symfony/config": "<6.1", "symfony/console": "<5.4", - "symfony/dependency-injection": "<6.3", + "symfony/dependency-injection": "<6.3.4", "symfony/doctrine-bridge": "<5.4", "symfony/form": "<5.4", "symfony/http-client": "<5.4", @@ -9191,7 +9191,7 @@ "symfony/config": "^6.1", "symfony/console": "^5.4|^6.0", "symfony/css-selector": "^5.4|^6.0", - "symfony/dependency-injection": "^6.3", + "symfony/dependency-injection": "^6.3.4", "symfony/dom-crawler": "^5.4|^6.0", "symfony/expression-language": "^5.4|^6.0", "symfony/finder": "^5.4|^6.0", @@ -9234,7 +9234,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/v6.3.3" + "source": "https://github.com/symfony/http-kernel/tree/v6.3.4" }, "funding": [ { @@ -9250,7 +9250,7 @@ "type": "tidelift" } ], - "time": "2023-07-31T10:33:00+00:00" + "time": "2023-08-26T13:54:49+00:00" }, { "name": "symfony/intl", @@ -10620,16 +10620,16 @@ }, { "name": "symfony/process", - "version": "v6.3.2", + "version": "v6.3.4", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "c5ce962db0d9b6e80247ca5eb9af6472bd4d7b5d" + "reference": "0b5c29118f2e980d455d2e34a5659f4579847c54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/c5ce962db0d9b6e80247ca5eb9af6472bd4d7b5d", - "reference": "c5ce962db0d9b6e80247ca5eb9af6472bd4d7b5d", + "url": "https://api.github.com/repos/symfony/process/zipball/0b5c29118f2e980d455d2e34a5659f4579847c54", + "reference": "0b5c29118f2e980d455d2e34a5659f4579847c54", "shasum": "" }, "require": { @@ -10661,7 +10661,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.3.2" + "source": "https://github.com/symfony/process/tree/v6.3.4" }, "funding": [ { @@ -10677,7 +10677,7 @@ "type": "tidelift" } ], - "time": "2023-07-12T16:00:22+00:00" + "time": "2023-08-07T10:39:22+00:00" }, { "name": "symfony/property-access", @@ -11229,16 +11229,16 @@ }, { "name": "symfony/security-bundle", - "version": "v6.3.3", + "version": "v6.3.4", "source": { "type": "git", "url": "https://github.com/symfony/security-bundle.git", - "reference": "b33382ca3034ee691dd0d882f214ae9e037f4427" + "reference": "31957477b289220a47880ead3727bf5cc059fa08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-bundle/zipball/b33382ca3034ee691dd0d882f214ae9e037f4427", - "reference": "b33382ca3034ee691dd0d882f214ae9e037f4427", + "url": "https://api.github.com/repos/symfony/security-bundle/zipball/31957477b289220a47880ead3727bf5cc059fa08", + "reference": "31957477b289220a47880ead3727bf5cc059fa08", "shasum": "" }, "require": { @@ -11319,7 +11319,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/v6.3.3" + "source": "https://github.com/symfony/security-bundle/tree/v6.3.4" }, "funding": [ { @@ -11335,7 +11335,7 @@ "type": "tidelift" } ], - "time": "2023-07-31T07:08:24+00:00" + "time": "2023-08-25T08:46:23+00:00" }, { "name": "symfony/security-core", @@ -11492,16 +11492,16 @@ }, { "name": "symfony/security-http", - "version": "v6.3.2", + "version": "v6.3.4", "source": { "type": "git", "url": "https://github.com/symfony/security-http.git", - "reference": "04d6b868786a56c1fadc52b003fe5a4f9ab3f3d0" + "reference": "0afb37c1120c1c46219bdbd1dd912fb4d48eaf7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-http/zipball/04d6b868786a56c1fadc52b003fe5a4f9ab3f3d0", - "reference": "04d6b868786a56c1fadc52b003fe5a4f9ab3f3d0", + "url": "https://api.github.com/repos/symfony/security-http/zipball/0afb37c1120c1c46219bdbd1dd912fb4d48eaf7d", + "reference": "0afb37c1120c1c46219bdbd1dd912fb4d48eaf7d", "shasum": "" }, "require": { @@ -11559,7 +11559,7 @@ "description": "Symfony Security Component - HTTP Integration", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-http/tree/v6.3.2" + "source": "https://github.com/symfony/security-http/tree/v6.3.4" }, "funding": [ { @@ -11575,20 +11575,20 @@ "type": "tidelift" } ], - "time": "2023-07-13T14:29:38+00:00" + "time": "2023-08-25T19:43:09+00:00" }, { "name": "symfony/serializer", - "version": "v6.3.3", + "version": "v6.3.4", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "33deb86d212893042d7758d452aa39d19ca0efe3" + "reference": "96d28a58d5a128bf77c54534b380eb7c92c8f846" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/33deb86d212893042d7758d452aa39d19ca0efe3", - "reference": "33deb86d212893042d7758d452aa39d19ca0efe3", + "url": "https://api.github.com/repos/symfony/serializer/zipball/96d28a58d5a128bf77c54534b380eb7c92c8f846", + "reference": "96d28a58d5a128bf77c54534b380eb7c92c8f846", "shasum": "" }, "require": { @@ -11653,7 +11653,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/v6.3.3" + "source": "https://github.com/symfony/serializer/tree/v6.3.4" }, "funding": [ { @@ -11669,7 +11669,7 @@ "type": "tidelift" } ], - "time": "2023-07-31T07:08:24+00:00" + "time": "2023-08-24T14:35:28+00:00" }, { "name": "symfony/service-contracts", @@ -11755,16 +11755,16 @@ }, { "name": "symfony/stimulus-bundle", - "version": "v2.10.0", + "version": "v2.11.0", "source": { "type": "git", "url": "https://github.com/symfony/stimulus-bundle.git", - "reference": "257ef052bebe491d7c29e9a4a8009edb82269e15" + "reference": "8c94fd88cbc9f4ee8177a2b04e69532a2e01145c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stimulus-bundle/zipball/257ef052bebe491d7c29e9a4a8009edb82269e15", - "reference": "257ef052bebe491d7c29e9a4a8009edb82269e15", + "url": "https://api.github.com/repos/symfony/stimulus-bundle/zipball/8c94fd88cbc9f4ee8177a2b04e69532a2e01145c", + "reference": "8c94fd88cbc9f4ee8177a2b04e69532a2e01145c", "shasum": "" }, "require": { @@ -11776,7 +11776,7 @@ "twig/twig": "^2.15.3|^3.4.3" }, "require-dev": { - "symfony/asset-mapper": "6.3.x-dev", + "symfony/asset-mapper": "^6.3", "symfony/framework-bundle": "^5.4|^6.0", "symfony/phpunit-bridge": "^5.4|^6.0", "symfony/twig-bundle": "^5.4|^6.0", @@ -11803,7 +11803,7 @@ "symfony-ux" ], "support": { - "source": "https://github.com/symfony/stimulus-bundle/tree/v2.10.0" + "source": "https://github.com/symfony/stimulus-bundle/tree/v2.11.0" }, "funding": [ { @@ -11819,7 +11819,7 @@ "type": "tidelift" } ], - "time": "2023-06-29T19:46:37+00:00" + "time": "2023-08-24T16:25:14+00:00" }, { "name": "symfony/stopwatch", @@ -12411,7 +12411,7 @@ }, { "name": "symfony/ux-translator", - "version": "v2.10.0", + "version": "v2.11.0", "source": { "type": "git", "url": "https://github.com/symfony/ux-translator.git", @@ -12467,7 +12467,7 @@ "symfony-ux" ], "support": { - "source": "https://github.com/symfony/ux-translator/tree/v2.10.0" + "source": "https://github.com/symfony/ux-translator/tree/v2.11.0" }, "funding": [ { @@ -12487,16 +12487,16 @@ }, { "name": "symfony/ux-turbo", - "version": "v2.10.0", + "version": "v2.11.0", "source": { "type": "git", "url": "https://github.com/symfony/ux-turbo.git", - "reference": "9762c373ed54c3642a4ae64b9254bc1c454f7da0" + "reference": "561c1e4ed176fb99af9e0dcde449336fc04e6b0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ux-turbo/zipball/9762c373ed54c3642a4ae64b9254bc1c454f7da0", - "reference": "9762c373ed54c3642a4ae64b9254bc1c454f7da0", + "url": "https://api.github.com/repos/symfony/ux-turbo/zipball/561c1e4ed176fb99af9e0dcde449336fc04e6b0a", + "reference": "561c1e4ed176fb99af9e0dcde449336fc04e6b0a", "shasum": "" }, "require": { @@ -12507,7 +12507,6 @@ "symfony/flex": "<1.13" }, "require-dev": { - "doctrine/annotations": "^1.12", "doctrine/doctrine-bundle": "^2.4.3", "doctrine/orm": "^2.8 | 3.0", "phpstan/phpstan": "^1.10", @@ -12519,6 +12518,7 @@ "symfony/messenger": "^5.4|^6.0", "symfony/panther": "^1.0|^2.0", "symfony/phpunit-bridge": "^5.4|^6.0", + "symfony/process": "^5.4|6.3.*", "symfony/property-access": "^5.4|^6.0", "symfony/security-core": "^5.4|^6.0", "symfony/stopwatch": "^5.4|^6.0", @@ -12563,7 +12563,7 @@ "turbo-stream" ], "support": { - "source": "https://github.com/symfony/ux-turbo/tree/v2.10.0" + "source": "https://github.com/symfony/ux-turbo/tree/v2.11.0" }, "funding": [ { @@ -12579,20 +12579,20 @@ "type": "tidelift" } ], - "time": "2023-06-19T13:59:38+00:00" + "time": "2023-08-24T16:25:14+00:00" }, { "name": "symfony/validator", - "version": "v6.3.2", + "version": "v6.3.4", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "b0c4ecf17d39eee1edfecc92299a03b9f5d5220b" + "reference": "0c8435154920b9bbe93bece675234c244cadf73b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/b0c4ecf17d39eee1edfecc92299a03b9f5d5220b", - "reference": "b0c4ecf17d39eee1edfecc92299a03b9f5d5220b", + "url": "https://api.github.com/repos/symfony/validator/zipball/0c8435154920b9bbe93bece675234c244cadf73b", + "reference": "0c8435154920b9bbe93bece675234c244cadf73b", "shasum": "" }, "require": { @@ -12659,7 +12659,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v6.3.2" + "source": "https://github.com/symfony/validator/tree/v6.3.4" }, "funding": [ { @@ -12675,20 +12675,20 @@ "type": "tidelift" } ], - "time": "2023-07-26T17:39:03+00:00" + "time": "2023-08-17T15:49:05+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.3.3", + "version": "v6.3.4", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "77fb4f2927f6991a9843633925d111147449ee7a" + "reference": "2027be14f8ae8eae999ceadebcda5b4909b81d45" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/77fb4f2927f6991a9843633925d111147449ee7a", - "reference": "77fb4f2927f6991a9843633925d111147449ee7a", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/2027be14f8ae8eae999ceadebcda5b4909b81d45", + "reference": "2027be14f8ae8eae999ceadebcda5b4909b81d45", "shasum": "" }, "require": { @@ -12743,7 +12743,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.3.3" + "source": "https://github.com/symfony/var-dumper/tree/v6.3.4" }, "funding": [ { @@ -12759,20 +12759,20 @@ "type": "tidelift" } ], - "time": "2023-07-31T07:08:24+00:00" + "time": "2023-08-24T14:51:05+00:00" }, { "name": "symfony/var-exporter", - "version": "v6.3.2", + "version": "v6.3.4", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "3400949782c0cb5b3e73aa64cfd71dde000beccc" + "reference": "df1f8aac5751871b83d30bf3e2c355770f8f0691" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/3400949782c0cb5b3e73aa64cfd71dde000beccc", - "reference": "3400949782c0cb5b3e73aa64cfd71dde000beccc", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/df1f8aac5751871b83d30bf3e2c355770f8f0691", + "reference": "df1f8aac5751871b83d30bf3e2c355770f8f0691", "shasum": "" }, "require": { @@ -12817,7 +12817,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.3.2" + "source": "https://github.com/symfony/var-exporter/tree/v6.3.4" }, "funding": [ { @@ -12833,7 +12833,7 @@ "type": "tidelift" } ], - "time": "2023-07-26T17:39:03+00:00" + "time": "2023-08-16T18:14:47+00:00" }, { "name": "symfony/web-link", @@ -14363,16 +14363,16 @@ }, { "name": "webonyx/graphql-php", - "version": "v15.6.0", + "version": "v15.6.2", "source": { "type": "git", "url": "https://github.com/webonyx/graphql-php.git", - "reference": "d950e2b542ee5c092c5d1375240b561282c06af1" + "reference": "0a917058620a197530b357c46d8d5943a054a37e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/d950e2b542ee5c092c5d1375240b561282c06af1", - "reference": "d950e2b542ee5c092c5d1375240b561282c06af1", + "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/0a917058620a197530b357c46d8d5943a054a37e", + "reference": "0a917058620a197530b357c46d8d5943a054a37e", "shasum": "" }, "require": { @@ -14389,7 +14389,7 @@ "nyholm/psr7": "^1.5", "phpbench/phpbench": "^1.2", "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "1.10.26", + "phpstan/phpstan": "1.10.28", "phpstan/phpstan-phpunit": "1.3.13", "phpstan/phpstan-strict-rules": "1.5.1", "phpunit/phpunit": "^9.5 || ^10", @@ -14424,7 +14424,7 @@ ], "support": { "issues": "https://github.com/webonyx/graphql-php/issues", - "source": "https://github.com/webonyx/graphql-php/tree/v15.6.0" + "source": "https://github.com/webonyx/graphql-php/tree/v15.6.2" }, "funding": [ { @@ -14432,7 +14432,7 @@ "type": "open_collective" } ], - "time": "2023-08-04T09:43:22+00:00" + "time": "2023-08-08T17:15:12+00:00" }, { "name": "willdurand/negotiation", @@ -15387,16 +15387,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.30", + "version": "1.10.32", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "2910afdd3fe33e5afd71c09f3fb0d0845b48c410" + "reference": "c47e47d3ab03137c0e121e77c4d2cb58672f6d44" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2910afdd3fe33e5afd71c09f3fb0d0845b48c410", - "reference": "2910afdd3fe33e5afd71c09f3fb0d0845b48c410", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c47e47d3ab03137c0e121e77c4d2cb58672f6d44", + "reference": "c47e47d3ab03137c0e121e77c4d2cb58672f6d44", "shasum": "" }, "require": { @@ -15445,7 +15445,7 @@ "type": "tidelift" } ], - "time": "2023-08-22T13:48:25+00:00" + "time": "2023-08-24T21:54:50+00:00" }, { "name": "phpstan/phpstan-doctrine", @@ -16692,16 +16692,16 @@ }, { "name": "symfony/dom-crawler", - "version": "v6.3.1", + "version": "v6.3.4", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "8aa333f41f05afc7fc285a976b58272fd90fc212" + "reference": "3fdd2a3d5fdc363b2e8dbf817f9726a4d013cbd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/8aa333f41f05afc7fc285a976b58272fd90fc212", - "reference": "8aa333f41f05afc7fc285a976b58272fd90fc212", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/3fdd2a3d5fdc363b2e8dbf817f9726a4d013cbd1", + "reference": "3fdd2a3d5fdc363b2e8dbf817f9726a4d013cbd1", "shasum": "" }, "require": { @@ -16739,7 +16739,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v6.3.1" + "source": "https://github.com/symfony/dom-crawler/tree/v6.3.4" }, "funding": [ { @@ -16755,7 +16755,7 @@ "type": "tidelift" } ], - "time": "2023-06-05T15:30:22+00:00" + "time": "2023-08-01T07:43:40+00:00" }, { "name": "symfony/maker-bundle", @@ -17015,16 +17015,16 @@ }, { "name": "symplify/easy-coding-standard", - "version": "12.0.6", + "version": "12.0.7", "source": { "type": "git", "url": "https://github.com/easy-coding-standard/easy-coding-standard.git", - "reference": "7372622f5d9126ec50b0923d7eb6b778fac575a5" + "reference": "79b5679f0dd758311770543969087b8dc46429ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/7372622f5d9126ec50b0923d7eb6b778fac575a5", - "reference": "7372622f5d9126ec50b0923d7eb6b778fac575a5", + "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/79b5679f0dd758311770543969087b8dc46429ee", + "reference": "79b5679f0dd758311770543969087b8dc46429ee", "shasum": "" }, "require": { @@ -17057,7 +17057,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.0.6" + "source": "https://github.com/easy-coding-standard/easy-coding-standard/tree/12.0.7" }, "funding": [ { @@ -17069,7 +17069,7 @@ "type": "github" } ], - "time": "2023-07-25T17:12:00+00:00" + "time": "2023-08-24T21:43:33+00:00" }, { "name": "vimeo/psalm", From 8fe3f4cf5c8adb9b72e388fcf72eb40b9ad15912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sat, 26 Aug 2023 22:57:50 +0200 Subject: [PATCH 12/62] Added permissions to control access to API and manage API tokens --- config/packages/security.yaml | 2 + config/permissions.yaml | 10 +++- src/Controller/UserSettingsController.php | 2 + src/Entity/UserSystem/User.php | 2 +- templates/users/_api_tokens.html.twig | 4 +- templates/users/user_settings.html.twig | 4 +- translations/messages.en.xlf | 62 +++++++++++++++-------- 7 files changed, 60 insertions(+), 26 deletions(-) diff --git a/config/packages/security.yaml b/config/packages/security.yaml index b60c632c..c2241f08 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -69,3 +69,5 @@ security: # We get into trouble with the U2F authentication, if the calls to the trees trigger an 2FA login # This settings should not do much harm, because a read only access to show available data structures is not really critical - { path: "^/\\w{2}/tree", role: PUBLIC_ACCESS } + # Restrict access to API to users, which has the API access permission + - { path: "^/api", allow_if: 'is_granted("@api.access_api") and is_authenticated()' } diff --git a/config/permissions.yaml b/config/permissions.yaml index cf363100..f0f1af32 100644 --- a/config/permissions.yaml +++ b/config/permissions.yaml @@ -254,6 +254,7 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co show_updates: label: "perm.system.show_available_updates" + attachments: label: "perm.part.attachments" operations: @@ -304,4 +305,11 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co label: "perm.revert_elements" alsoSet: ['read_profiles', 'edit_profiles', 'create_profiles', 'delete_profiles'] - + api: + label: "perm.api" + operations: + access_api: + label: "perm.api.access_api" + manage_tokens: + label: "perm.api.manage_tokens" + alsoSet: ['access_api'] \ No newline at end of file diff --git a/src/Controller/UserSettingsController.php b/src/Controller/UserSettingsController.php index 3f9d89bd..bb03b71f 100644 --- a/src/Controller/UserSettingsController.php +++ b/src/Controller/UserSettingsController.php @@ -406,6 +406,8 @@ class UserSettingsController extends AbstractController #[Route('/api_token/create', name: 'user_api_token_create')] public function addApiToken(Request $request, EntityManagerInterface $entityManager): Response { + $this->denyAccessUnlessGranted('@api.manage_tokens'); + $token = new ApiToken(); $token->setUser($this->getUser()); diff --git a/src/Entity/UserSystem/User.php b/src/Entity/UserSystem/User.php index ae4f90d0..31a8e6e3 100644 --- a/src/Entity/UserSystem/User.php +++ b/src/Entity/UserSystem/User.php @@ -102,7 +102,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe final public const ID_ANONYMOUS = 1; #[Groups(['user:read'])] - protected ?int $id; + protected ?int $id = null; #[Groups(['user:read'])] protected ?\DateTimeInterface $lastModified = null; diff --git a/templates/users/_api_tokens.html.twig b/templates/users/_api_tokens.html.twig index d52b8970..fa199744 100644 --- a/templates/users/_api_tokens.html.twig +++ b/templates/users/_api_tokens.html.twig @@ -55,7 +55,9 @@ {% endif %} - {% trans %}api_token.create_new{% endtrans %} + + {% trans %}api_token.create_new{% endtrans %} + \ No newline at end of file diff --git a/templates/users/user_settings.html.twig b/templates/users/user_settings.html.twig index 359b993e..0f218a1b 100644 --- a/templates/users/user_settings.html.twig +++ b/templates/users/user_settings.html.twig @@ -77,5 +77,7 @@ - {% include "users/_api_tokens.html.twig" %} + {% if is_granted("@api.access_api") %} + {% include "users/_api_tokens.html.twig" %} + {% endif %} {% endblock %} diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index c55baaad..ccf1336f 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -731,10 +731,10 @@ user.edit.tfa.disable_tfa_message - This will disable <b>all active two-factor authentication methods of the user</b> and delete the <b>backup codes</b>! -<br> -The user will have to set up all two-factor authentication methods again and print new backup codes! <br><br> -<b>Only do this if you are absolutely sure about the identity of the user (seeking help), otherwise the account could be compromised by an attacker!</b> + all active two-factor authentication methods of the user and delete the backup codes! +
+The user will have to set up all two-factor authentication methods again and print new backup codes!

+Only do this if you are absolutely sure about the identity of the user (seeking help), otherwise the account could be compromised by an attacker!]]>
@@ -11326,67 +11326,67 @@ Element 3 - + tfa_u2f.add_key.registration_error An error occurred during the registration of the security key. Try again or use another security key! - + log.target_type.none None - + ui.darkmode.light Light - + ui.darkmode.dark Dark - + ui.darkmode.auto Auto (decide based on system settings) - + label_generator.no_lines_given No text content given! The labels will remain empty. - + user.password_strength.very_weak Very weak - + user.password_strength.weak Weak - + user.password_strength.medium Medium - + user.password_strength.strong Strong - + user.password_strength.very_strong Very strong @@ -11580,25 +11580,25 @@ Please note, that you can not impersonate a disabled user. If you try you will g - + update_manager.new_version_available.title New version available - + update_manager.new_version_available.text A new version of Part-DB is available. Check it out here - + update_manager.new_version_available.only_administrators_can_see Only administrators can see this message. - + perm.system.show_available_updates Show available Part-DB updates @@ -11730,22 +11730,40 @@ Please note, that you can not impersonate a disabled user. If you try you will g - + project.build.dont_check_quantity Do not check quantities - + project.build.dont_check_quantity.help If this option is selected, the given withdraw quantities are used as given, no matter if more or less parts are actually required to build this project. - + part_list.action.invert_selection Invert selection + + + perm.api + API + + + + + perm.api.access_api + Access API + + + + + perm.api.manage_tokens + Manage API tokens + + From 3e693642b6583b34ee1e365976c1b3d774ebedb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sat, 26 Aug 2023 23:19:35 +0200 Subject: [PATCH 13/62] Allow to delete API tokens --- src/Controller/UserSettingsController.php | 41 +++++++++++++ templates/users/_api_tokens.html.twig | 73 +++++++++++++---------- translations/messages.en.xlf | 24 ++++++++ 3 files changed, 108 insertions(+), 30 deletions(-) diff --git a/src/Controller/UserSettingsController.php b/src/Controller/UserSettingsController.php index bb03b71f..c0d9f66d 100644 --- a/src/Controller/UserSettingsController.php +++ b/src/Controller/UserSettingsController.php @@ -407,6 +407,8 @@ class UserSettingsController extends AbstractController public function addApiToken(Request $request, EntityManagerInterface $entityManager): Response { $this->denyAccessUnlessGranted('@api.manage_tokens'); + //When user change its settings, he should be logged in fully. + $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY'); $token = new ApiToken(); $token->setUser($this->getUser()); @@ -450,4 +452,43 @@ class UserSettingsController extends AbstractController 'secret' => $secret, ]); } + + #[Route(path: '/api_token/delete', name: 'user_api_tokens_delete', methods: ['DELETE'])] + public function apiTokenRemove(Request $request, EntityManagerInterface $entityManager): Response + { + $this->denyAccessUnlessGranted('@api.manage_tokens'); + //When user change its settings, he should be logged in fully. + $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY'); + + $user = $this->getUser(); + if (!$user instanceof User) { + throw new RuntimeException('This controller only works only for Part-DB User objects!'); + } + + if (!$this->isCsrfTokenValid('delete'.$user->getID(), $request->request->get('_token'))) { + $this->addFlash('error', 'csfr_invalid'); + return $this->redirectToRoute('user_settings'); + } + + //Extract the token id from the request + $token_id = $request->request->getInt('token_id'); + + $token = $entityManager->find(ApiToken::class, $token_id); + if ($token === null) { + $this->addFlash('error', 'tfa_u2f.u2f_delete.not_existing'); + return $this->redirectToRoute('user_settings'); + } + //User can only delete its own API tokens + if ($token->getUser() !== $user) { + $this->addFlash('error', 'tfa_u2f.u2f_delete.access_denied'); + return $this->redirectToRoute('user_settings'); + } + + //Do the actual deletion + $entityManager->remove($token); + $entityManager->flush(); + + $this->addFlash('success', 'api_tokens.deleted'); + return $this->redirectToRoute('user_settings'); + } } diff --git a/templates/users/_api_tokens.html.twig b/templates/users/_api_tokens.html.twig index fa199744..43df3205 100644 --- a/templates/users/_api_tokens.html.twig +++ b/templates/users/_api_tokens.html.twig @@ -21,38 +21,51 @@

{% trans %}user.settings.api_tokens.no_api_tokens_yet{% endtrans %} {% else %} - - - - - - - - - - - - - - {% for api_token in user.apiTokens %} - {# @var api_token \App\Entity\UserSystem\ApiToken #} + + + +
{% trans %}api_tokens.name{% endtrans %}{% trans %}api_tokens.access_level{% endtrans %}{% trans %}api_tokens.expiration_date{% endtrans %}{% trans %}tfa_u2f.keys.added_date{% endtrans %}{% trans %}api_tokens.last_time_used{% endtrans %}
+ - - - - - + + + + + + - {% endfor %} - -
{{ api_token.name }}{{ api_token.level.translationKey|trans }} - {{ _self.format_date(api_token.validUntil) }} - {% if api_token.valid %} - {% trans %}api_token.valid{% endtrans %} - {% else %} - {% trans %}api_token.expired{% endtrans %} - {% endif %} - {{ _self.format_date(api_token.addedDate) }}{{ _self.format_date(api_token.lastTimeUsed) }}{% trans %}api_tokens.name{% endtrans %}{% trans %}api_tokens.access_level{% endtrans %}{% trans %}api_tokens.expiration_date{% endtrans %}{% trans %}tfa_u2f.keys.added_date{% endtrans %}{% trans %}api_tokens.last_time_used{% endtrans %}
+ + + + {% for api_token in user.apiTokens %} + {# @var api_token \App\Entity\UserSystem\ApiToken #} + + {{ api_token.name }} + {{ api_token.level.translationKey|trans }} + + {{ _self.format_date(api_token.validUntil) }} + {% if api_token.valid %} + {% trans %}api_token.valid{% endtrans %} + {% else %} + {% trans %}api_token.expired{% endtrans %} + {% endif %} + + {{ _self.format_date(api_token.addedDate) }} + {{ _self.format_date(api_token.lastTimeUsed) }} + + + + + {% endfor %} + + + {% endif %} diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index ccf1336f..9ac5b7f5 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -11765,5 +11765,29 @@ Please note, that you can not impersonate a disabled user. If you try you will g Manage API tokens + + + user.settings.api_tokens.delete.title + Do you really want to delete this API token? + + + + + user.settings.api_tokens.delete + Delete + + + + + user.settings.api_tokens.delete.message + The application, which uses this API token, will no longer have access to Part-DB. This action can not be undone! + + + + + api_tokens.deleted + API token deleted successfully! + + From 56d120cd083a476167a430f4a5c94d38b0df9c89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sat, 26 Aug 2023 23:21:28 +0200 Subject: [PATCH 14/62] Fixed styling of no API tokens yet message --- templates/users/_api_tokens.html.twig | 1 + translations/messages.en.xlf | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/templates/users/_api_tokens.html.twig b/templates/users/_api_tokens.html.twig index 43df3205..c84543b2 100644 --- a/templates/users/_api_tokens.html.twig +++ b/templates/users/_api_tokens.html.twig @@ -20,6 +20,7 @@ {% if user.apiTokens.empty %}

{% trans %}user.settings.api_tokens.no_api_tokens_yet{% endtrans %} +

{% else %}
API token deleted successfully! + + + user.settings.api_tokens.no_api_tokens_yet + No API tokens configured yet. + + From fc6643bd6fe0b9a1ee4912e05a9f7cd00652c3d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 28 Aug 2023 21:20:59 +0200 Subject: [PATCH 15/62] Added system to restrict permissions based on API token level --- config/permissions.yaml | 63 ++++++++- .../PermissionsConfiguration.php | 1 + src/Entity/UserSystem/ApiTokenLevel.php | 4 +- src/Security/Voter/PermissionVoter.php | 20 +-- src/Services/UserSystem/PermissionManager.php | 37 ++++- src/Services/UserSystem/VoterHelper.php | 110 +++++++++++++++ .../UserSystem/PermissionManagerTest.php | 19 ++- tests/Services/UserSystem/VoterHelperTest.php | 133 ++++++++++++++++++ 8 files changed, 374 insertions(+), 13 deletions(-) create mode 100644 src/Services/UserSystem/VoterHelper.php create mode 100644 tests/Services/UserSystem/VoterHelperTest.php diff --git a/config/permissions.yaml b/config/permissions.yaml index f0f1af32..b8970556 100644 --- a/config/permissions.yaml +++ b/config/permissions.yaml @@ -25,27 +25,35 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co # 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'] + apiTokenRole: ROLE_API_READ_ONLY edit: label: "perm.edit" alsoSet: ['read', 'parts_stock.withdraw', 'parts_stock.add', 'parts_stock.move'] + apiTokenRole: ROLE_API_EDIT create: label: "perm.create" alsoSet: ['read', 'edit'] + apiTokenRole: ROLE_API_EDIT delete: label: "perm.delete" alsoSet: ['read', 'edit'] + apiTokenRole: ROLE_API_EDIT change_favorite: label: "perm.part.change_favorite" alsoSet: ['edit'] + apiTokenRole: ROLE_API_EDIT show_history: label: "perm.part.show_history" alsoSet: ['read'] + apiTokenRole: ROLE_API_READ_ONLY revert_element: label: "perm.revert_elements" alsoSet: ["read", "edit", "create", "delete", "show_history"] + apiTokenRole: ROLE_API_EDIT import: label: "perm.import" alsoSet: ["read", "edit", "create"] + apiTokenRole: ROLE_API_EDIT parts_stock: group: "data" @@ -53,10 +61,13 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co operations: withdraw: label: "perm.parts_stock.withdraw" + apiTokenRole: ROLE_API_EDIT add: label: "perm.parts_stock.add" + apiTokenRole: ROLE_API_EDIT move: label: "perm.parts_stock.move" + apiTokenRole: ROLE_API_EDIT storelocations: &PART_CONTAINING @@ -65,23 +76,30 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co operations: read: label: "perm.read" + apiTokenRole: ROLE_API_READ_ONLY edit: label: "perm.edit" alsoSet: 'read' + apiTokenRole: ROLE_API_EDIT create: label: "perm.create" alsoSet: ['read', 'edit'] + apiTokenRole: ROLE_API_EDIT delete: label: "perm.delete" alsoSet: ['read', 'edit'] + apiTokenRole: ROLE_API_EDIT show_history: label: "perm.show_history" + apiTokenRole: ROLE_API_READ_ONLY revert_element: label: "perm.revert_elements" alsoSet: ["read", "edit", "create", "delete", "show_history"] + apiTokenRole: ROLE_API_EDIT import: label: "perm.import" alsoSet: [ "read", "edit", "create" ] + apiTokenRole: ROLE_API_EDIT footprints: <<: *PART_CONTAINING @@ -145,6 +163,7 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co create_parts: label: "perm.part.info_providers.create_parts" alsoSet: ['parts.create'] + apiTokenRole: ROLE_API_EDIT groups: label: "perm.groups" @@ -152,26 +171,34 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co operations: read: label: "perm.read" + apiTokenRole: ROLE_API_ADMIN edit: label: "perm.edit" alsoSet: 'read' + apiTokenRole: ROLE_API_ADMIN create: label: "perm.create" alsoSet: ['read', 'edit'] + apiTokenRole: ROLE_API_ADMIN delete: label: "perm.delete" alsoSet: ['read', 'delete'] + apiTokenRole: ROLE_API_ADMIN edit_permissions: label: "perm.edit_permissions" alsoSet: ['read', 'edit'] + apiTokenRole: ROLE_API_ADMIN show_history: label: "perm.show_history" + apiTokenRole: ROLE_API_ADMIN revert_element: label: "perm.revert_elements" alsoSet: ["read", "edit", "create", "delete", "edit_permissions", "show_history"] + apiTokenRole: ROLE_API_ADMIN import: label: "perm.import" alsoSet: [ "read", "edit", "create" ] + apiTokenRole: ROLE_API_ADMIN users: label: "perm.users" @@ -179,37 +206,49 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co operations: read: label: "perm.read" + apiTokenRole: ROLE_API_ADMIN create: label: "perm.create" alsoSet: ['read', 'edit_username', 'edit_infos'] + apiTokenRole: ROLE_API_ADMIN delete: label: "perm.delete" alsoSet: ['read', 'edit_username', 'edit_infos'] + apiTokenRole: ROLE_API_ADMIN edit_username: label: "perm.users.edit_user_name" alsoSet: ['read'] + apiTokenRole: ROLE_API_ADMIN edit_infos: label: "perm.users.edit_infos" alsoSet: 'read' + apiTokenRole: ROLE_API_ADMIN edit_permissions: label: "perm.users.edit_permissions" alsoSet: 'read' + apiTokenRole: ROLE_API_ADMIN set_password: label: "perm.users.set_password" alsoSet: 'read' + apiTokenRole: ROLE_API_FULL impersonate: label: "perm.users.impersonate" alsoSet: ['set_password'] + apiTokenRole: ROLE_API_FULL change_user_settings: label: "perm.users.change_user_settings" + apiTokenRole: ROLE_API_ADMIN show_history: label: "perm.show_history" + apiTokenRole: ROLE_API_ADMIN revert_element: label: "perm.revert_elements" alsoSet: ["read", "create", "delete", "edit_permissions", "show_history", "edit_infos", "edit_username"] + apiTokenRole: ROLE_API_ADMIN import: label: "perm.import" alsoSet: [ "read", "create" ] + apiTokenRole: ROLE_API_ADMIN #database: # label: "perm.database" @@ -244,15 +283,20 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co operations: show_logs: label: "perm.show_logs" + apiTokenRole: ROLE_API_ADMIN delete_logs: label: "perm.delete_logs" alsoSet: 'show_logs' + apiTokenRole: ROLE_API_ADMIN server_infos: label: "perm.server_infos" + apiTokenRole: ROLE_API_ADMIN manage_oauth_tokens: label: "Manage OAuth tokens" + apiTokenRole: ROLE_API_ADMIN show_updates: label: "perm.system.show_available_updates" + apiTokenRole: ROLE_API_ADMIN attachments: @@ -260,56 +304,73 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co operations: show_private: label: "perm.attachments.show_private" + apiTokenRole: ROLE_API_READ_ONLY list_attachments: label: "perm.attachments.list_attachments" alsoSet: ['attachment_types.read'] + apiTokenRole: ROLE_API_READ_ONLY self: label: "perm.self" operations: edit_infos: label: "perm.self.edit_infos" + apiTokenRole: ROLE_API_FULL edit_username: label: "perm.self.edit_username" + apiTokenRole: ROLE_API_FULL show_permissions: label: "perm.self.show_permissions" + apiTokenRole: ROLE_API_READ_ONLY show_logs: label: "perm.self.show_logs" + apiTokenRole: ROLE_API_FULL labels: label: "perm.labels" operations: create_labels: label: "perm.self.create_labels" + apiTokenRole: ROLE_API_READ_ONLY edit_options: label: "perm.self.edit_options" alsoSet: ['create_labels'] + apiTokenRole: ROLE_API_READ_ONLY read_profiles: label: "perm.self.read_profiles" + apiTokenRole: ROLE_API_READ_ONLY edit_profiles: label: "perm.self.edit_profiles" alsoSet: ['read_profiles'] + apiTokenRole: ROLE_API_EDIT create_profiles: label: "perm.self.create_profiles" alsoSet: ['read_profiles', 'edit_profiles'] + apiTokenRole: ROLE_API_EDIT delete_profiles: label: "perm.self.delete_profiles" alsoSet: ['read_profiles', 'edit_profiles', 'create_profiles'] + apiTokenRole: ROLE_API_EDIT use_twig: label: "perm.labels.use_twig" alsoSet: ['create_labels', 'edit_options'] + apiTokenRole: ROLE_API_ADMIN show_history: label: "perm.show_history" alsoSet: ['read_profiles'] + apiTokenRole: ROLE_API_READ_ONLY revert_element: label: "perm.revert_elements" alsoSet: ['read_profiles', 'edit_profiles', 'create_profiles', 'delete_profiles'] + apiTokenRole: ROLE_API_EDIT api: label: "perm.api" operations: access_api: label: "perm.api.access_api" + apiTokenRole: ROLE_API_READ_ONLY manage_tokens: label: "perm.api.manage_tokens" - alsoSet: ['access_api'] \ No newline at end of file + alsoSet: ['access_api'] + apiTokenRole: ROLE_API_FULL \ No newline at end of file diff --git a/src/Configuration/PermissionsConfiguration.php b/src/Configuration/PermissionsConfiguration.php index 307e3794..c069ab24 100644 --- a/src/Configuration/PermissionsConfiguration.php +++ b/src/Configuration/PermissionsConfiguration.php @@ -55,6 +55,7 @@ final class PermissionsConfiguration implements ConfigurationInterface ->scalarNode('name')->end() ->scalarNode('label')->end() ->scalarNode('bit')->end() + ->scalarNode('apiTokenRole')->defaultNull()->end() ->arrayNode('alsoSet') ->beforeNormalization()->castToArray()->end()->scalarPrototype()->end(); diff --git a/src/Entity/UserSystem/ApiTokenLevel.php b/src/Entity/UserSystem/ApiTokenLevel.php index 8289e57c..3f997300 100644 --- a/src/Entity/UserSystem/ApiTokenLevel.php +++ b/src/Entity/UserSystem/ApiTokenLevel.php @@ -27,6 +27,7 @@ enum ApiTokenLevel: int { private const ROLE_READ_ONLY = 'ROLE_API_READ_ONLY'; private const ROLE_EDIT = 'ROLE_API_EDIT'; + private const ROLE_ADMIN = 'ROLE_API_ADMIN'; private const ROLE_FULL = 'ROLE_API_FULL'; /** @@ -56,7 +57,8 @@ enum ApiTokenLevel: int return match ($this) { self::READ_ONLY => [self::ROLE_READ_ONLY], self::EDIT => [self::ROLE_READ_ONLY, self::ROLE_EDIT], - self::FULL => [self::ROLE_READ_ONLY, self::ROLE_EDIT, self::ROLE_FULL], + self::ADMIN => [self::ROLE_READ_ONLY, self::ROLE_EDIT, self::ROLE_ADMIN], + self::FULL => [self::ROLE_READ_ONLY, self::ROLE_EDIT, self::ROLE_ADMIN, self::ROLE_FULL], }; } diff --git a/src/Security/Voter/PermissionVoter.php b/src/Security/Voter/PermissionVoter.php index 018c4f92..f7d84bf7 100644 --- a/src/Security/Voter/PermissionVoter.php +++ b/src/Security/Voter/PermissionVoter.php @@ -23,26 +23,28 @@ declare(strict_types=1); namespace App\Security\Voter; use App\Entity\UserSystem\User; +use App\Services\UserSystem\VoterHelper; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** * This voter allows you to directly check permissions from the permission structure, without passing an object. * This use the syntax like "@permission.op" * However you should use the "normal" object based voters if possible, because they are needed for a future ACL system. */ -class PermissionVoter extends ExtendedVoter +class PermissionVoter extends Voter { - /** - * Similar to voteOnAttribute, but checking for the anonymous user is already done. - * The current user (or the anonymous user) is passed by $user. - * - * @param string $attribute - */ - protected function voteOnUser(string $attribute, $subject, User $user): bool + public function __construct(private readonly VoterHelper $helper) + { + + } + + protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool { $attribute = ltrim($attribute, '@'); [$perm, $op] = explode('.', $attribute); - return $this->resolver->inherit($user, $perm, $op) ?? false; + return $this->helper->isGranted($token, $perm, $op); } /** diff --git a/src/Services/UserSystem/PermissionManager.php b/src/Services/UserSystem/PermissionManager.php index a995dad7..83c806b6 100644 --- a/src/Services/UserSystem/PermissionManager.php +++ b/src/Services/UserSystem/PermissionManager.php @@ -28,6 +28,7 @@ use App\Entity\UserSystem\Group; use App\Entity\UserSystem\User; use App\Security\Interfaces\HasPermissionsInterface; use InvalidArgumentException; +use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\Config\ConfigCache; use Symfony\Component\Config\Definition\Processor; use Symfony\Component\Config\Resource\FileResource; @@ -41,7 +42,7 @@ use Symfony\Component\Yaml\Yaml; */ class PermissionManager { - protected $permission_structure; + protected array $permission_structure; protected string $cache_file; /** @@ -125,6 +126,40 @@ class PermissionManager return null; //The inherited value is never resolved. Should be treated as false, in Voters. } + /** + * Same as inherit(), but it checks if the access token has the required role. + * @param User $user the user for which the operation should be checked + * @param array $roles The roles associated with the authentication token + * @param string $permission the name of the permission for which should be checked + * @param string $operation the name of the operation for which should be checked + * + * @return bool|null true, if the user is allowed to do the operation (ALLOW), false if not (DISALLOW), and null, + * if the value is set to inherit + */ + public function inheritWithAPILevel(User $user, array $roles, string $permission, string $operation): ?bool + { + //Check that the permission/operation combination is valid + if (! $this->isValidOperation($permission, $operation)) { + throw new InvalidArgumentException('The permission/operation combination "'.$permission.'/'.$operation.'" is not valid!'); + } + + //Get what API level we require for the permission/operation + $level_role = $this->permission_structure['perms'][$permission]['operations'][$operation]['apiTokenRole']; + + //When no role was set (or it is null), then the operation is blocked for API access + if (null === $level_role) { + return false; + } + + //Otherwise check if the token has the required role, if not, then the operation is blocked for API access + if (!in_array($level_role, $roles, true)) { + return false; + } + + //If we have the required role, then we can check the permission + return $this->inherit($user, $permission, $operation); + } + /** * Sets the new value for the operation. * diff --git a/src/Services/UserSystem/VoterHelper.php b/src/Services/UserSystem/VoterHelper.php new file mode 100644 index 00000000..3f78a1aa --- /dev/null +++ b/src/Services/UserSystem/VoterHelper.php @@ -0,0 +1,110 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Services\UserSystem; + +use App\Entity\UserSystem\User; +use App\Repository\UserRepository; +use App\Security\ApiTokenAuthenticatedToken; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +final class VoterHelper +{ + private readonly UserRepository $userRepository; + + public function __construct(private readonly PermissionManager $permissionManager, private readonly EntityManagerInterface $entityManager) + { + $this->userRepository = $this->entityManager->getRepository(User::class); + } + + /** + * Checks if the operation on the given permission is granted for the given token. + * Similar to isGrantedTrinary, but returns false if the permission is not granted. + * @param TokenInterface $token The token to check + * @param string $permission The permission to check + * @param string $operation The operation to check + * @return bool + */ + public function isGranted(TokenInterface $token, string $permission, string $operation): bool + { + return $this->isGrantedTrinary($token, $permission, $operation) ?? false; + } + + /** + * Checks if the operation on the given permission is granted for the given token. + * The result is returned in trinary value, where null means inherted from the parent. + * @param TokenInterface $token The token to check + * @param string $permission The permission to check + * @param string $operation The operation to check + * @return bool|null The result of the check. Null means inherted from the parent. + */ + public function isGrantedTrinary(TokenInterface $token, string $permission, string $operation): ?bool + { + $user = $token->getUser(); + + if ($user instanceof User) { + //A disallowed user is not allowed to do anything... + if ($user->isDisabled()) { + return false; + } + } else { + //Try to resolve the user from the token + $user = $this->resolveUser($token); + } + + //If the token is a APITokenAuthenticated + if ($token instanceof ApiTokenAuthenticatedToken) { + //Use the special API token checker + return $this->permissionManager->inheritWithAPILevel($user, $token->getRoleNames(), $permission, $operation); + } + + //Otherwise use the normal permission checker + return $this->permissionManager->inherit($user, $permission, $operation); + } + + /** + * Resolves the user from the given token. If the token is anonymous, the anonymous user is returned. + * @return User + */ + public function resolveUser(TokenInterface $token): User + { + $user = $token->getUser(); + //If the user is a User entity, just return it + if ($user instanceof User) { + return $user; + } + + //If the user is null, return the anonymous user + if ($user === null) { + $user = $this->userRepository->getAnonymousUser(); + if (!$user instanceof User) { + throw new \RuntimeException('The anonymous user could not be resolved.'); + } + return $user; + } + + //Otherwise throw an exception + throw new \RuntimeException('The user could not be resolved.'); + } +} \ No newline at end of file diff --git a/tests/Services/UserSystem/PermissionManagerTest.php b/tests/Services/UserSystem/PermissionManagerTest.php index b24801df..9d4aa11b 100644 --- a/tests/Services/UserSystem/PermissionManagerTest.php +++ b/tests/Services/UserSystem/PermissionManagerTest.php @@ -52,7 +52,11 @@ class PermissionManagerTest extends WebTestCase ->setPermissionValue('parts', 'edit', false) //edit ->setPermissionValue('parts', 'create', null) //create ->setPermissionValue('parts', 'move', null) //move - ->setPermissionValue('parts', 'delete', null); //delete + ->setPermissionValue('parts', 'delete', null) //delete + + ->setPermissionValue('footprints', 'edit', true) + ->setPermissionValue('footprints', 'create', false) + ; $this->user = $this->createMock(User::class); $this->user->method('getPermissions')->willReturn($user_perms); @@ -170,6 +174,19 @@ class PermissionManagerTest extends WebTestCase $this->assertNull($this->service->inherit($this->user_withoutGroup, 'parts', 'delete')); } + public function testInheritWithAPILevel(): void + { + //If no API roles are given, access should be prevented + $this->assertFalse($this->service->inheritWithAPILevel($this->user, [], 'parts', 'read')); + //Allow access with roles + $this->assertTrue($this->service->inheritWithAPILevel($this->user, ['ROLE_API_READ_ONLY', 'ROLE_API_FULL'], 'parts', 'read')); + + //Block access if the token has not the sufficient level + $this->assertFalse($this->service->inheritWithAPILevel($this->user, ['ROLE_API_READ_ONLY'], 'footprints', 'edit')); + //And allow with role + $this->assertTrue($this->service->inheritWithAPILevel($this->user, ['ROLE_API_READ_ONLY', 'ROLE_API_EDIT'], 'footprints', 'edit')); + } + public function testSetPermission(): void { $user = new User(); diff --git a/tests/Services/UserSystem/VoterHelperTest.php b/tests/Services/UserSystem/VoterHelperTest.php new file mode 100644 index 00000000..28bb9ba8 --- /dev/null +++ b/tests/Services/UserSystem/VoterHelperTest.php @@ -0,0 +1,133 @@ +. + */ + +namespace App\Tests\Services\UserSystem; + +use App\Entity\UserSystem\ApiToken; +use App\Entity\UserSystem\ApiTokenLevel; +use App\Entity\UserSystem\PermissionData; +use App\Entity\UserSystem\User; +use App\Security\ApiTokenAuthenticatedToken; +use App\Services\UserSystem\VoterHelper; +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Symfony\Component\Security\Core\Authentication\Token\NullToken; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; +use Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken; + +class VoterHelperTest extends KernelTestCase +{ + + protected ?VoterHelper $service = null; + + protected ?User $user = null; + + protected function setUp(): void + { + //Get a service instance. + self::bootKernel(); + $this->service = self::getContainer()->get(VoterHelper::class); + + //Set up a mocked user + $user_perms = new PermissionData(); + $user_perms->setPermissionValue('parts', 'read', true) //read + ->setPermissionValue('parts', 'edit', false) //edit + ->setPermissionValue('parts', 'create', null) //create + ->setPermissionValue('parts', 'move', null) //move + ->setPermissionValue('parts', 'delete', null) //delete + + ->setPermissionValue('footprints', 'edit', true) + ->setPermissionValue('footprints', 'create', false) + ; + + $this->user = $this->createMock(User::class); + $this->user->method('getPermissions')->willReturn($user_perms); + } + + public function testResolveUserAnonUser(): void + { + //If the user is null, the anonymous user should be returned. + + $anonymousToken = new NullToken(); + $this->assertNull($anonymousToken->getUser()); + $user = $this->service->resolveUser($anonymousToken); + //Ensure that this is the anonymous user. + $this->assertNotNull($user); + $this->assertTrue($user->isAnonymousUser()); + } + + public function testResolveUser(): void + { + //For a token with a user, the user should be returned. + $token = new PostAuthenticationToken($this->user, 'main', ['ROLE_USER']); + $this->assertSame($this->user, $token->getUser()); + $user = $this->service->resolveUser($token); + $this->assertSame($this->user, $user); + } + + public function testIsGrantedTrinaryNonAPI(): void + { + //For a UserNamePasswordToken everything should work as expected. + $token = new UsernamePasswordToken($this->user, 'main'); + $this->assertTrue($this->service->isGrantedTrinary($token, 'parts', 'read')); + $this->assertFalse($this->service->isGrantedTrinary($token, 'parts', 'edit')); + $this->assertNull($this->service->isGrantedTrinary($token, 'parts', 'create')); + } + + public function testIsGrantedTrinaryReadOnlyAPI(): void + { + //Create a API token + $api_token = new ApiToken(); + $api_token->setLevel(ApiTokenLevel::READ_ONLY)->setName('Test Token'); + //Create an auth token + $token = new ApiTokenAuthenticatedToken($this->user, 'main', ['ROLE_USER'], $api_token); + + //The permissions should be readonly + $this->assertTrue($this->service->isGrantedTrinary($token, 'parts', 'read')); + $this->assertFalse($this->service->isGrantedTrinary($token, 'parts', 'edit')); + $this->assertFalse($this->service->isGrantedTrinary($token, 'parts', 'create')); + $this->assertFalse($this->service->isGrantedTrinary($token, 'footprints', 'edit')); + } + + public function testIsGrantedTrinaryAdminAPI(): void + { + //Create a API token + $api_token = new ApiToken(); + $api_token->setLevel(ApiTokenLevel::FULL)->setName('Test Token'); + //Create an auth token + $token = new ApiTokenAuthenticatedToken($this->user, 'main', ['ROLE_USER'], $api_token); + + //The permissions should be readonly + $this->assertTrue($this->service->isGrantedTrinary($token, 'parts', 'read')); + $this->assertFalse($this->service->isGrantedTrinary($token, 'parts', 'edit')); + $this->assertNull($this->service->isGrantedTrinary($token, 'parts', 'create')); + $this->assertTrue($this->service->isGrantedTrinary($token, 'footprints', 'edit')); + } + + + public function testIsGrantedNonAPI(): void + { + //Same as testIsGrantedTrinaryNonAPI, but every non-true value should return false. + $token = new UsernamePasswordToken($this->user, 'main'); + $this->assertTrue($this->service->isGranted($token, 'parts', 'read')); + $this->assertFalse($this->service->isGranted($token, 'parts', 'edit')); + $this->assertFalse($this->service->isGranted($token, 'parts', 'create')); + } +} From 6be55d18371db77cf72f0547f909f2c5e52e529e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 28 Aug 2023 22:00:25 +0200 Subject: [PATCH 16/62] Use the new VoterHelper in voters --- src/Security/Voter/AttachmentVoter.php | 25 +++---- src/Security/Voter/ExtendedVoter.php | 68 ------------------- src/Security/Voter/GroupVoter.php | 16 +++-- .../Voter/HasAccessPermissionsVoter.php | 15 +++- src/Security/Voter/ImpersonateUserVoter.php | 20 ++---- src/Security/Voter/LabelProfileVoter.php | 14 ++-- src/Security/Voter/LogEntryVoter.php | 20 +++--- src/Security/Voter/OrderdetailVoter.php | 12 ++-- src/Security/Voter/ParameterVoter.php | 12 ++-- src/Security/Voter/PartLotVoter.php | 18 ++--- src/Security/Voter/PartVoter.php | 15 ++-- src/Security/Voter/PermissionVoter.php | 4 +- src/Security/Voter/PricedetailVoter.php | 16 ++--- src/Security/Voter/StructureVoter.php | 16 +++-- src/Security/Voter/UserVoter.php | 23 +++++-- src/Services/UserSystem/VoterHelper.php | 14 ++++ 16 files changed, 146 insertions(+), 162 deletions(-) delete mode 100644 src/Security/Voter/ExtendedVoter.php diff --git a/src/Security/Voter/AttachmentVoter.php b/src/Security/Voter/AttachmentVoter.php index 0b32188c..5750855c 100644 --- a/src/Security/Voter/AttachmentVoter.php +++ b/src/Security/Voter/AttachmentVoter.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Security\Voter; +use App\Services\UserSystem\VoterHelper; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Attachments\AttachmentContainingDBElement; use App\Entity\Attachments\Attachment; @@ -37,29 +38,21 @@ use App\Entity\Attachments\ProjectAttachment; use App\Entity\Attachments\StorelocationAttachment; use App\Entity\Attachments\SupplierAttachment; use App\Entity\Attachments\UserAttachment; -use App\Entity\UserSystem\User; -use App\Services\UserSystem\PermissionManager; -use Doctrine\ORM\EntityManagerInterface; use RuntimeException; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; + use function in_array; -class AttachmentVoter extends ExtendedVoter +final class AttachmentVoter extends Voter { - public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, protected Security $security) + public function __construct(private readonly Security $security, private readonly VoterHelper $helper) { - parent::__construct($resolver, $entityManager); } - /** - * Similar to voteOnAttribute, but checking for the anonymous user is already done. - * The current user (or the anonymous user) is passed by $user. - * - * @param string $attribute - */ - protected function voteOnUser(string $attribute, $subject, User $user): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool { - //return $this->resolver->inherit($user, 'attachments', $attribute) ?? false; //This voter only works for attachments if (!is_a($subject, Attachment::class, true)) { @@ -67,7 +60,7 @@ class AttachmentVoter extends ExtendedVoter } if ($attribute === 'show_private') { - return $this->resolver->inherit($user, 'attachments', 'show_private') ?? false; + return $this->helper->isGranted($token, 'attachments', 'show_private'); } @@ -113,7 +106,7 @@ class AttachmentVoter extends ExtendedVoter throw new RuntimeException('Encountered unknown Parameter type: ' . $subject); } - return $this->resolver->inherit($user, $param, $this->mapOperation($attribute)) ?? false; + return $this->helper->isGranted($token, $param, $this->mapOperation($attribute)); } return false; diff --git a/src/Security/Voter/ExtendedVoter.php b/src/Security/Voter/ExtendedVoter.php deleted file mode 100644 index 279944fc..00000000 --- a/src/Security/Voter/ExtendedVoter.php +++ /dev/null @@ -1,68 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace App\Security\Voter; - -use App\Entity\UserSystem\User; -use App\Repository\UserRepository; -use App\Services\UserSystem\PermissionManager; -use Doctrine\ORM\EntityManagerInterface; -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Authorization\Voter\Voter; - -/** - * The purpose of this class is, to use the anonymous user from DB in the case, that nobody is logged in. - */ -abstract class ExtendedVoter extends Voter -{ - public function __construct(protected PermissionManager $resolver, protected EntityManagerInterface $entityManager) - { - } - - final protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool - { - $user = $token->getUser(); - - //An allowed user is not allowed to do anything... - if ($user instanceof User && $user->isDisabled()) { - return false; - } - - // if the user is anonymous (meaning $user is null), we use the anonymous user. - if (!$user instanceof User) { - /** @var UserRepository $repo */ - $repo = $this->entityManager->getRepository(User::class); - $user = $repo->getAnonymousUser(); - if (!$user instanceof User) { - return false; - } - } - - return $this->voteOnUser($attribute, $subject, $user); - } - - /** - * Similar to voteOnAttribute, but checking for the anonymous user is already done. - * The current user (or the anonymous user) is passed by $user. - */ - abstract protected function voteOnUser(string $attribute, $subject, User $user): bool; -} diff --git a/src/Security/Voter/GroupVoter.php b/src/Security/Voter/GroupVoter.php index e1e21543..ef81524f 100644 --- a/src/Security/Voter/GroupVoter.php +++ b/src/Security/Voter/GroupVoter.php @@ -24,18 +24,26 @@ namespace App\Security\Voter; use App\Entity\UserSystem\Group; use App\Entity\UserSystem\User; +use App\Services\UserSystem\VoterHelper; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; -class GroupVoter extends ExtendedVoter +final class GroupVoter extends Voter { + + public function __construct(private readonly VoterHelper $helper) + { + } + /** * Similar to voteOnAttribute, but checking for the anonymous user is already done. * The current user (or the anonymous user) is passed by $user. * * @param string $attribute */ - protected function voteOnUser(string $attribute, $subject, User $user): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool { - return $this->resolver->inherit($user, 'groups', $attribute) ?? false; + return $this->helper->isGranted($token, 'groups', $attribute); } /** @@ -49,7 +57,7 @@ class GroupVoter extends ExtendedVoter protected function supports(string $attribute, $subject): bool { if (is_a($subject, Group::class, true)) { - return $this->resolver->isValidOperation('groups', $attribute); + return $this->helper->isValidOperation('groups', $attribute); } return false; diff --git a/src/Security/Voter/HasAccessPermissionsVoter.php b/src/Security/Voter/HasAccessPermissionsVoter.php index 3ea6dd36..8145f7b1 100644 --- a/src/Security/Voter/HasAccessPermissionsVoter.php +++ b/src/Security/Voter/HasAccessPermissionsVoter.php @@ -24,18 +24,27 @@ declare(strict_types=1); namespace App\Security\Voter; use App\Entity\UserSystem\User; +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\Voter; /** * This voter implements a virtual role, which can be used if the user has any permission set to allowed. * We use this to restrict access to the homepage. */ -class HasAccessPermissionsVoter extends ExtendedVoter +final class HasAccessPermissionsVoter extends Voter { public const ROLE = "HAS_ACCESS_PERMISSIONS"; - protected function voteOnUser(string $attribute, $subject, User $user): bool + public function __construct(private readonly PermissionManager $permissionManager, private readonly VoterHelper $helper) { - return $this->resolver->hasAnyPermissionSetToAllowInherited($user); + } + + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + { + $user = $this->helper->resolveUser($token); + return $this->permissionManager->hasAnyPermissionSetToAllowInherited($user); } protected function supports(string $attribute, mixed $subject): bool diff --git a/src/Security/Voter/ImpersonateUserVoter.php b/src/Security/Voter/ImpersonateUserVoter.php index f1392568..00751cfc 100644 --- a/src/Security/Voter/ImpersonateUserVoter.php +++ b/src/Security/Voter/ImpersonateUserVoter.php @@ -25,36 +25,26 @@ namespace App\Security\Voter; use App\Entity\UserSystem\User; 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\Voter; use Symfony\Component\Security\Core\User\UserInterface; -class ImpersonateUserVoter extends Voter +final class ImpersonateUserVoter extends Voter { - public function __construct(private PermissionManager $permissionManager) + public function __construct(private readonly VoterHelper $helper) { } protected function supports(string $attribute, mixed $subject): bool { - return $attribute == 'CAN_SWITCH_USER' + return $attribute === 'CAN_SWITCH_USER' && $subject instanceof UserInterface; } protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool { - $user = $token->getUser(); - - if (!$user instanceof User || !$subject instanceof UserInterface) { - return false; - } - - //An disabled user is not allowed to do anything... - if ($user->isDisabled()) { - return false; - } - - return $this->permissionManager->inherit($user, 'users', 'impersonate') ?? false; + return $this->helper->isGranted($token, 'users', 'impersonate'); } } \ No newline at end of file diff --git a/src/Security/Voter/LabelProfileVoter.php b/src/Security/Voter/LabelProfileVoter.php index 5a3699a2..7f27d8d0 100644 --- a/src/Security/Voter/LabelProfileVoter.php +++ b/src/Security/Voter/LabelProfileVoter.php @@ -43,8 +43,11 @@ namespace App\Security\Voter; use App\Entity\LabelSystem\LabelProfile; use App\Entity\UserSystem\User; +use App\Services\UserSystem\VoterHelper; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; -class LabelProfileVoter extends ExtendedVoter +final class LabelProfileVoter extends Voter { protected const MAPPING = [ 'read' => 'read_profiles', @@ -55,9 +58,12 @@ class LabelProfileVoter extends ExtendedVoter 'revert_element' => 'revert_element', ]; - protected function voteOnUser(string $attribute, $subject, User $user): bool + public function __construct(private readonly VoterHelper $helper) + {} + + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool { - return $this->resolver->inherit($user, 'labels', self::MAPPING[$attribute]) ?? false; + return $this->helper->isGranted($token, 'labels', self::MAPPING[$attribute]); } protected function supports($attribute, $subject): bool @@ -67,7 +73,7 @@ class LabelProfileVoter extends ExtendedVoter return false; } - return $this->resolver->isValidOperation('labels', self::MAPPING[$attribute]); + return $this->helper->isValidOperation('labels', self::MAPPING[$attribute]); } return false; diff --git a/src/Security/Voter/LogEntryVoter.php b/src/Security/Voter/LogEntryVoter.php index 20c34863..e60b66d1 100644 --- a/src/Security/Voter/LogEntryVoter.php +++ b/src/Security/Voter/LogEntryVoter.php @@ -22,41 +22,45 @@ declare(strict_types=1); namespace App\Security\Voter; +use App\Services\UserSystem\VoterHelper; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\LogSystem\AbstractLogEntry; use App\Entity\UserSystem\User; use App\Services\UserSystem\PermissionManager; use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; -class LogEntryVoter extends ExtendedVoter +final class LogEntryVoter extends Voter { final public const ALLOWED_OPS = ['read', 'show_details', 'delete']; - public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, private readonly Security $security) + public function __construct(private readonly Security $security, private readonly VoterHelper $helper) { - parent::__construct($resolver, $entityManager); } - protected function voteOnUser(string $attribute, $subject, User $user): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool { + $user = $this->helper->resolveUser($token); + if (!$subject instanceof AbstractLogEntry) { throw new \InvalidArgumentException('The subject must be an instance of '.AbstractLogEntry::class); } if ('delete' === $attribute) { - return $this->resolver->inherit($user, 'system', 'delete_logs') ?? false; + return $this->helper->isGranted($token, 'system', 'delete_logs'); } if ('read' === $attribute) { //Allow read of the users own log entries if ( $subject->getUser() === $user - && $this->resolver->inherit($user, 'self', 'show_logs') + && $this->helper->isGranted($token, 'self', 'show_logs') ) { return true; } - return $this->resolver->inherit($user, 'system', 'show_logs') ?? false; + return $this->helper->isGranted($token, 'system', 'show_logs'); } if ('show_details' === $attribute) { @@ -67,7 +71,7 @@ class LogEntryVoter extends ExtendedVoter } //In other cases, this behaves like the read permission - return $this->voteOnUser('read', $subject, $user); + return $this->voteOnAttribute('read', $subject, $token); } return false; diff --git a/src/Security/Voter/OrderdetailVoter.php b/src/Security/Voter/OrderdetailVoter.php index 96ff609e..625732b5 100644 --- a/src/Security/Voter/OrderdetailVoter.php +++ b/src/Security/Voter/OrderdetailVoter.php @@ -41,23 +41,25 @@ declare(strict_types=1); namespace App\Security\Voter; +use App\Services\UserSystem\VoterHelper; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Parts\Part; use App\Entity\PriceInformations\Orderdetail; use App\Entity\UserSystem\User; use App\Services\UserSystem\PermissionManager; use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; -class OrderdetailVoter extends ExtendedVoter +final class OrderdetailVoter extends Voter { - public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, protected Security $security) + public function __construct(private readonly Security $security, private readonly VoterHelper $helper) { - parent::__construct($resolver, $entityManager); } protected const ALLOWED_PERMS = ['read', 'edit', 'create', 'delete', 'show_history', 'revert_element']; - protected function voteOnUser(string $attribute, $subject, User $user): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool { if (! is_a($subject, Orderdetail::class, true)) { throw new \RuntimeException('This voter can only handle Orderdetail objects!'); @@ -73,7 +75,7 @@ class OrderdetailVoter extends ExtendedVoter //If we have no part associated use the generic part permission if (is_string($subject) || !$subject->getPart() instanceof Part) { - return $this->resolver->inherit($user, 'parts', $operation) ?? false; + return $this->helper->isGranted($token, 'parts', $operation); } //Otherwise vote on the part diff --git a/src/Security/Voter/ParameterVoter.php b/src/Security/Voter/ParameterVoter.php index 6f752518..96bd6060 100644 --- a/src/Security/Voter/ParameterVoter.php +++ b/src/Security/Voter/ParameterVoter.php @@ -22,6 +22,7 @@ declare(strict_types=1); */ namespace App\Security\Voter; +use App\Services\UserSystem\VoterHelper; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Base\AbstractDBElement; use App\Entity\Parameters\AbstractParameter; @@ -40,16 +41,17 @@ use App\Entity\UserSystem\User; use App\Services\UserSystem\PermissionManager; use Doctrine\ORM\EntityManagerInterface; use RuntimeException; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; -class ParameterVoter extends ExtendedVoter +final class ParameterVoter extends Voter { - public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, protected Security $security) + public function __construct(private readonly Security $security, private readonly VoterHelper $helper) { - parent::__construct($resolver, $entityManager); } - protected function voteOnUser(string $attribute, $subject, User $user): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool { //return $this->resolver->inherit($user, 'attachments', $attribute) ?? false; @@ -104,7 +106,7 @@ class ParameterVoter extends ExtendedVoter throw new RuntimeException('Encountered unknown Parameter type: ' . (is_object($subject) ? $subject::class : $subject)); } - return $this->resolver->inherit($user, $param, $attribute) ?? false; + return $this->helper->isGranted($token, $param, $attribute); } protected function supports(string $attribute, $subject): bool diff --git a/src/Security/Voter/PartLotVoter.php b/src/Security/Voter/PartLotVoter.php index c9ddb89e..36fb230f 100644 --- a/src/Security/Voter/PartLotVoter.php +++ b/src/Security/Voter/PartLotVoter.php @@ -41,31 +41,31 @@ declare(strict_types=1); namespace App\Security\Voter; +use App\Services\UserSystem\VoterHelper; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; use App\Entity\UserSystem\User; use App\Services\UserSystem\PermissionManager; use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; -class PartLotVoter extends ExtendedVoter +final class PartLotVoter extends Voter { - public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, protected Security $security) + public function __construct(private readonly Security $security, private readonly VoterHelper $helper) { - parent::__construct($resolver, $entityManager); } protected const ALLOWED_PERMS = ['read', 'edit', 'create', 'delete', 'show_history', 'revert_element', 'withdraw', 'add', 'move']; - protected function voteOnUser(string $attribute, $subject, User $user): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool { - if (! is_a($subject, PartLot::class, true)) { - throw new \RuntimeException('This voter can only handle PartLot objects!'); - } + $user = $this->helper->resolveUser($token); if (in_array($attribute, ['withdraw', 'add', 'move'], true)) { - $base_permission = $this->resolver->inherit($user, 'parts_stock', $attribute) ?? false; + $base_permission = $this->helper->isGranted($token, 'parts_stock', $attribute); $lot_permission = true; //If the lot has an owner, we need to check if the user is the owner of the lot to be allowed to withdraw it. @@ -86,7 +86,7 @@ class PartLotVoter extends ExtendedVoter //If we have no part associated use the generic part permission if (is_string($subject) || !$subject->getPart() instanceof Part) { - return $this->resolver->inherit($user, 'parts', $operation) ?? false; + return $this->helper->isGranted($token, 'parts', $operation); } //Otherwise vote on the part diff --git a/src/Security/Voter/PartVoter.php b/src/Security/Voter/PartVoter.php index 878fc6a4..c2c37563 100644 --- a/src/Security/Voter/PartVoter.php +++ b/src/Security/Voter/PartVoter.php @@ -24,29 +24,36 @@ namespace App\Security\Voter; use App\Entity\Parts\Part; use App\Entity\UserSystem\User; +use App\Services\UserSystem\VoterHelper; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** * A Voter that votes on Part entities. * * See parts permissions for valid operations. */ -class PartVoter extends ExtendedVoter +final class PartVoter extends Voter { final public const READ = 'read'; + public function __construct(private readonly VoterHelper $helper) + { + } + protected function supports($attribute, $subject): bool { if (is_a($subject, Part::class, true)) { - return $this->resolver->isValidOperation('parts', $attribute); + return $this->helper->isValidOperation('parts', $attribute); } //Allow class name as subject return false; } - protected function voteOnUser(string $attribute, $subject, User $user): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool { //Null concealing operator means, that no - return $this->resolver->inherit($user, 'parts', $attribute) ?? false; + return $this->helper->isGranted($token, 'parts', $attribute); } } diff --git a/src/Security/Voter/PermissionVoter.php b/src/Security/Voter/PermissionVoter.php index f7d84bf7..01b34215 100644 --- a/src/Security/Voter/PermissionVoter.php +++ b/src/Security/Voter/PermissionVoter.php @@ -32,7 +32,7 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter; * This use the syntax like "@permission.op" * However you should use the "normal" object based voters if possible, because they are needed for a future ACL system. */ -class PermissionVoter extends Voter +final class PermissionVoter extends Voter { public function __construct(private readonly VoterHelper $helper) { @@ -62,7 +62,7 @@ class PermissionVoter extends Voter $attribute = ltrim($attribute, '@'); [$perm, $op] = explode('.', $attribute); - $valid = $this->resolver->isValidOperation($perm, $op); + $valid = $this->helper->isValidOperation($perm, $op); //if an invalid operation is encountered, throw an exception so the developer knows it if(!$valid) { diff --git a/src/Security/Voter/PricedetailVoter.php b/src/Security/Voter/PricedetailVoter.php index c4def709..9be5c4ed 100644 --- a/src/Security/Voter/PricedetailVoter.php +++ b/src/Security/Voter/PricedetailVoter.php @@ -41,6 +41,7 @@ declare(strict_types=1); namespace App\Security\Voter; +use App\Services\UserSystem\VoterHelper; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\PriceInformations\Orderdetail; use App\Entity\Parts\Part; @@ -48,22 +49,19 @@ use App\Entity\PriceInformations\Pricedetail; use App\Entity\UserSystem\User; use App\Services\UserSystem\PermissionManager; use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; -class PricedetailVoter extends ExtendedVoter +final class PricedetailVoter extends Voter { - public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, protected Security $security) + public function __construct(private readonly Security $security, private readonly VoterHelper $helper) { - parent::__construct($resolver, $entityManager); } protected const ALLOWED_PERMS = ['read', 'edit', 'create', 'delete', 'show_history', 'revert_element']; - protected function voteOnUser(string $attribute, $subject, User $user): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool { - if (!is_a($subject, Pricedetail::class, true)) { - throw new \RuntimeException('This voter can only handle Pricedetails objects!'); - } - $operation = match ($attribute) { 'read' => 'read', 'edit', 'create', 'delete' => 'edit', @@ -74,7 +72,7 @@ class PricedetailVoter extends ExtendedVoter //If we have no part associated use the generic part permission if (is_string($subject) || !$subject->getOrderdetail() instanceof Orderdetail || !$subject->getOrderdetail()->getPart() instanceof Part) { - return $this->resolver->inherit($user, 'parts', $operation) ?? false; + return $this->helper->isGranted($token, 'parts', $operation); } //Otherwise vote on the part diff --git a/src/Security/Voter/StructureVoter.php b/src/Security/Voter/StructureVoter.php index 79cef811..477bc28d 100644 --- a/src/Security/Voter/StructureVoter.php +++ b/src/Security/Voter/StructureVoter.php @@ -32,10 +32,14 @@ use App\Entity\Parts\Storelocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; use App\Entity\UserSystem\User; +use App\Services\UserSystem\VoterHelper; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; + use function get_class; use function is_object; -class StructureVoter extends ExtendedVoter +final class StructureVoter extends Voter { protected const OBJ_PERM_MAP = [ AttachmentType::class => 'attachment_types', @@ -49,6 +53,10 @@ class StructureVoter extends ExtendedVoter MeasurementUnit::class => 'measurement_units', ]; + public function __construct(private readonly VoterHelper $helper) + { + } + /** * Determines if the attribute and subject are supported by this voter. * @@ -62,7 +70,7 @@ class StructureVoter extends ExtendedVoter if (is_object($subject) || is_string($subject)) { $permission_name = $this->instanceToPermissionName($subject); //If permission name is null, then the subject is not supported - return (null !== $permission_name) && $this->resolver->isValidOperation($permission_name, $attribute); + return (null !== $permission_name) && $this->helper->isValidOperation($permission_name, $attribute); } return false; @@ -99,10 +107,10 @@ class StructureVoter extends ExtendedVoter * * @param string $attribute */ - protected function voteOnUser(string $attribute, $subject, User $user): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool { $permission_name = $this->instanceToPermissionName($subject); //Just resolve the permission - return $this->resolver->inherit($user, $permission_name, $attribute) ?? false; + return $this->helper->isGranted($token, $permission_name, $attribute); } } diff --git a/src/Security/Voter/UserVoter.php b/src/Security/Voter/UserVoter.php index e98e1701..f6502f6e 100644 --- a/src/Security/Voter/UserVoter.php +++ b/src/Security/Voter/UserVoter.php @@ -23,10 +23,19 @@ declare(strict_types=1); namespace App\Security\Voter; use App\Entity\UserSystem\User; +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\Voter; + use function in_array; -class UserVoter extends ExtendedVoter +final class UserVoter extends Voter { + public function __construct(private readonly VoterHelper $helper, private readonly PermissionManager $resolver) + { + } + /** * Determines if the attribute and subject are supported by this voter. * @@ -57,8 +66,10 @@ class UserVoter extends ExtendedVoter * * @param string $attribute */ - protected function voteOnUser(string $attribute, $subject, User $user): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool { + $user = $this->helper->resolveUser($token); + if ($attribute === 'info') { //Every logged-in user (non-anonymous) can see the info pages of other users if (!$user->isAnonymousUser()) { @@ -71,9 +82,9 @@ class UserVoter extends ExtendedVoter //Check if the checked user is the user itself if (($subject instanceof User) && $subject->getID() === $user->getID() && - $this->resolver->isValidOperation('self', $attribute)) { + $this->helper->isValidOperation('self', $attribute)) { //Then we also need to check the self permission - $tmp = $this->resolver->inherit($user, 'self', $attribute) ?? false; + $tmp = $this->helper->isGranted($token, 'self', $attribute); //But if the self value is not allowed then use just the user value: if ($tmp) { return $tmp; @@ -81,8 +92,8 @@ class UserVoter extends ExtendedVoter } //Else just check user permission: - if ($this->resolver->isValidOperation('users', $attribute)) { - return $this->resolver->inherit($user, 'users', $attribute) ?? false; + if ($this->helper->isValidOperation('users', $attribute)) { + return $this->helper->isGranted($token, 'users', $attribute); } return false; diff --git a/src/Services/UserSystem/VoterHelper.php b/src/Services/UserSystem/VoterHelper.php index 3f78a1aa..c13cf22a 100644 --- a/src/Services/UserSystem/VoterHelper.php +++ b/src/Services/UserSystem/VoterHelper.php @@ -107,4 +107,18 @@ final class VoterHelper //Otherwise throw an exception throw new \RuntimeException('The user could not be resolved.'); } + + /** + * Checks if the permission operation combination with the given names is existing. + * Just a proxy to the permission manager. + * + * @param string $permission the name of the permission which should be checked + * @param string $operation the name of the operation which should be checked + * + * @return bool true if the given permission operation combination is existing + */ + public function isValidOperation(string $permission, string $operation): bool + { + return $this->permissionManager->isValidOperation($permission, $operation); + } } \ No newline at end of file From f265b9d19dccd0e026214edee09eab1d7ba6cf0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 28 Aug 2023 22:39:29 +0200 Subject: [PATCH 17/62] Fixed PHPstan issues --- phpstan.neon | 11 +++++-- src/Controller/UserSettingsController.php | 3 ++ .../Attachments/MeasurementUnitAttachment.php | 5 ++- src/Security/ApiTokenAuthenticator.php | 17 ++++------ tests/object-manager.php | 32 +++++++++++++++++++ 5 files changed, 53 insertions(+), 15 deletions(-) create mode 100644 tests/object-manager.php diff --git a/phpstan.neon b/phpstan.neon index db118378..95eee3c0 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -19,6 +19,10 @@ parameters: symfony: container_xml_path: '%rootDir%/../../../var/cache/dev/App_KernelDevDebugContainer.xml' + doctrine: + objectManagerLoader: tests/object-manager.php + allowNullablePropertyForRequiredField: true + checkUninitializedProperties: true checkFunctionNameCase: true @@ -48,8 +52,11 @@ parameters: ignoreErrors: # Ignore errors caused by complex mapping with AbstractStructuralDBElement - '#AbstractStructuralDBElement does not have a field named \$parent#' - - '#AbstractStructuralDBElement does not have a field named \$name#' + #- '#AbstractStructuralDBElement does not have a field named \$name#' # Ignore errors related to the use of the ParametersTrait in Part entity - '#expects .*PartParameter, .*AbstractParameter given.#' - - '#Part::getParameters\(\) should return .*AbstractParameter#' \ No newline at end of file + - '#Part::getParameters\(\) should return .*AbstractParameter#' + + # Ignore doctrine type mapping mismatch + - '#Property .* type mapping mismatch: property can contain .* but database expects .*#' \ No newline at end of file diff --git a/src/Controller/UserSettingsController.php b/src/Controller/UserSettingsController.php index c0d9f66d..a70efbff 100644 --- a/src/Controller/UserSettingsController.php +++ b/src/Controller/UserSettingsController.php @@ -411,6 +411,9 @@ class UserSettingsController extends AbstractController $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY'); $token = new ApiToken(); + if (!$this->getUser() instanceof User) { + throw new RuntimeException('This controller only works only for Part-DB User objects!'); + } $token->setUser($this->getUser()); $secret = null; diff --git a/src/Entity/Attachments/MeasurementUnitAttachment.php b/src/Entity/Attachments/MeasurementUnitAttachment.php index 58467f86..7b7c4f83 100644 --- a/src/Entity/Attachments/MeasurementUnitAttachment.php +++ b/src/Entity/Attachments/MeasurementUnitAttachment.php @@ -36,9 +36,8 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; class MeasurementUnitAttachment extends Attachment { final public const ALLOWED_ELEMENT_CLASS = MeasurementUnit::class; - /** - * @var Manufacturer|null the element this attachment is associated with - */ + + #[ORM\ManyToOne(targetEntity: MeasurementUnit::class, inversedBy: 'attachments')] #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] protected ?AttachmentContainingDBElement $element = null; diff --git a/src/Security/ApiTokenAuthenticator.php b/src/Security/ApiTokenAuthenticator.php index 6d3dc09c..d274ca1a 100644 --- a/src/Security/ApiTokenAuthenticator.php +++ b/src/Security/ApiTokenAuthenticator.php @@ -118,12 +118,8 @@ class ApiTokenAuthenticator implements AuthenticatorInterface public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response { - if (null !== $this->translator) { - $errorMessage = $this->translator->trans($exception->getMessageKey(), $exception->getMessageData(), - 'security'); - } else { - $errorMessage = strtr($exception->getMessageKey(), $exception->getMessageData()); - } + $errorMessage = $this->translator->trans($exception->getMessageKey(), $exception->getMessageData(), + 'security'); return new Response( null, @@ -152,8 +148,9 @@ class ApiTokenAuthenticator implements AuthenticatorInterface return sprintf('Bearer %s', implode(',', $values)); } -public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response -{ - return null; -} + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + return null; + } } \ No newline at end of file diff --git a/tests/object-manager.php b/tests/object-manager.php new file mode 100644 index 00000000..1e335268 --- /dev/null +++ b/tests/object-manager.php @@ -0,0 +1,32 @@ +. + */ + +declare(strict_types=1); + +use App\Kernel; +use Symfony\Component\Dotenv\Dotenv; + +require __DIR__ . '/../vendor/autoload.php'; + +(new Dotenv())->bootEnv(__DIR__ . '/../.env'); + +$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']); +$kernel->boot(); +return $kernel->getContainer()->get('doctrine')->getManager(); \ No newline at end of file From 879b702fc14ab1572ee4b639738e95eee4bdd702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 28 Aug 2023 22:47:15 +0200 Subject: [PATCH 18/62] Fixed PHPunit tests --- tests/Serializer/StructuralElementDenormalizerTest.php | 5 +++-- .../StructuralElementFromNameDenormalizerTest.php | 4 +++- tests/Serializer/StructuralElementNormalizerTest.php | 7 +++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/tests/Serializer/StructuralElementDenormalizerTest.php b/tests/Serializer/StructuralElementDenormalizerTest.php index 19cf4ea8..fc1c0f1d 100644 --- a/tests/Serializer/StructuralElementDenormalizerTest.php +++ b/tests/Serializer/StructuralElementDenormalizerTest.php @@ -44,8 +44,9 @@ class StructuralElementDenormalizerTest extends WebTestCase $this->assertFalse($this->service->supportsDenormalization('doesnt_matter', Category::class, 'json', ['groups' => ['import']])); $this->assertFalse($this->service->supportsDenormalization(['name' => 'Test'], Category::class, 'json', ['groups' => ['simple']])); - //Denormalizer should only be active, when we use the import function - $this->assertTrue($this->service->supportsDenormalization(['name' => 'Test'], Category::class, 'json', ['groups' => ['import']])); + //Denormalizer should only be active, when we use the import function and partdb_import is set + $this->assertFalse($this->service->supportsDenormalization(['name' => 'Test'], Category::class, 'json', ['groups' => ['import']])); + $this->assertTrue($this->service->supportsDenormalization(['name' => 'Test'], Category::class, 'json', ['groups' => ['import'], 'partdb_import' => true])); } /** diff --git a/tests/Serializer/StructuralElementFromNameDenormalizerTest.php b/tests/Serializer/StructuralElementFromNameDenormalizerTest.php index 918c0339..b344508c 100644 --- a/tests/Serializer/StructuralElementFromNameDenormalizerTest.php +++ b/tests/Serializer/StructuralElementFromNameDenormalizerTest.php @@ -45,7 +45,9 @@ class StructuralElementFromNameDenormalizerTest extends WebTestCase $this->assertFalse($this->service->supportsDenormalization('doesnt_matter', \stdClass::class)); $this->assertFalse($this->service->supportsDenormalization(['a' => 'b'], Category::class)); - $this->assertTrue($this->service->supportsDenormalization('doesnt_matter', Category::class)); + //The denormalizer should only be active, when we are doing a file import operation + $this->assertFalse($this->service->supportsDenormalization('doesnt_matter', Category::class)); + $this->assertTrue($this->service->supportsDenormalization('doesnt_matter', Category::class, 'json', ['partdb_import' => true])); } public function testDenormalizeCreateNew(): void diff --git a/tests/Serializer/StructuralElementNormalizerTest.php b/tests/Serializer/StructuralElementNormalizerTest.php index 8d4df05a..b151f249 100644 --- a/tests/Serializer/StructuralElementNormalizerTest.php +++ b/tests/Serializer/StructuralElementNormalizerTest.php @@ -71,8 +71,11 @@ class StructuralElementNormalizerTest extends WebTestCase //Normalizer must only support StructuralElement objects (and child classes) $this->assertFalse($this->service->supportsNormalization(new \stdClass())); $this->assertFalse($this->service->supportsNormalization(new Part())); - $this->assertTrue($this->service->supportsNormalization(new Category())); - $this->assertTrue($this->service->supportsNormalization(new Footprint())); + + //Must only be active when export is enabled + $this->assertFalse($this->service->supportsNormalization(new Category())); + $this->assertTrue($this->service->supportsNormalization(new Category(), null, ['partdb_export' => true])); + $this->assertTrue($this->service->supportsNormalization(new Footprint(), null, ['partdb_export' => true])); } } From 7b6ba376673519c96eeb9caa4921b3a763aec47f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 28 Aug 2023 23:06:37 +0200 Subject: [PATCH 19/62] Allow to cache support status of voters This should increase the performance a bit --- src/Security/Voter/AttachmentVoter.php | 14 +++++++++++++- src/Security/Voter/GroupVoter.php | 10 ++++++++++ src/Security/Voter/HasAccessPermissionsVoter.php | 5 +++++ src/Security/Voter/ImpersonateUserVoter.php | 10 ++++++++++ src/Security/Voter/LabelProfileVoter.php | 10 ++++++++++ src/Security/Voter/LogEntryVoter.php | 10 ++++++++++ src/Security/Voter/OrderdetailVoter.php | 10 ++++++++++ src/Security/Voter/ParameterVoter.php | 15 ++++++++++++++- src/Security/Voter/PartLotVoter.php | 10 ++++++++++ src/Security/Voter/PartVoter.php | 10 ++++++++++ src/Security/Voter/PermissionVoter.php | 6 ++++++ src/Security/Voter/PricedetailVoter.php | 10 ++++++++++ src/Security/Voter/StructureVoter.php | 5 +++++ src/Security/Voter/UserVoter.php | 10 ++++++++++ 14 files changed, 133 insertions(+), 2 deletions(-) diff --git a/src/Security/Voter/AttachmentVoter.php b/src/Security/Voter/AttachmentVoter.php index 5750855c..54f6cc70 100644 --- a/src/Security/Voter/AttachmentVoter.php +++ b/src/Security/Voter/AttachmentVoter.php @@ -47,6 +47,8 @@ use function in_array; final class AttachmentVoter extends Voter { + private const ALLOWED_ATTRIBUTES = ['read', 'view', 'edit', 'delete', 'create', 'show_private', 'show_history']; + public function __construct(private readonly Security $security, private readonly VoterHelper $helper) { } @@ -134,10 +136,20 @@ final class AttachmentVoter extends Voter { if (is_a($subject, Attachment::class, true)) { //These are the allowed attributes - return in_array($attribute, ['read', 'view', 'edit', 'delete', 'create', 'show_private', 'show_history'], true); + return in_array($attribute, self::ALLOWED_ATTRIBUTES, true); } //Allow class name as subject return false; } + + public function supportsAttribute(string $attribute): bool + { + return in_array($attribute, self::ALLOWED_ATTRIBUTES, true); + } + + public function supportsType(string $subjectType): bool + { + return $subjectType === 'string' || is_a($subjectType, Attachment::class, true); + } } diff --git a/src/Security/Voter/GroupVoter.php b/src/Security/Voter/GroupVoter.php index ef81524f..96ae20d0 100644 --- a/src/Security/Voter/GroupVoter.php +++ b/src/Security/Voter/GroupVoter.php @@ -62,4 +62,14 @@ final class GroupVoter extends Voter return false; } + + public function supportsAttribute(string $attribute): bool + { + return $this->helper->isValidOperation('groups', $attribute); + } + + public function supportsType(string $subjectType): bool + { + return $subjectType === 'string' || is_a($subjectType, Group::class, true); + } } diff --git a/src/Security/Voter/HasAccessPermissionsVoter.php b/src/Security/Voter/HasAccessPermissionsVoter.php index 8145f7b1..16639d2f 100644 --- a/src/Security/Voter/HasAccessPermissionsVoter.php +++ b/src/Security/Voter/HasAccessPermissionsVoter.php @@ -51,4 +51,9 @@ final class HasAccessPermissionsVoter extends Voter { return $attribute === self::ROLE; } + + public function supportsAttribute(string $attribute): bool + { + return $attribute === self::ROLE; + } } \ No newline at end of file diff --git a/src/Security/Voter/ImpersonateUserVoter.php b/src/Security/Voter/ImpersonateUserVoter.php index 00751cfc..eebcfcc3 100644 --- a/src/Security/Voter/ImpersonateUserVoter.php +++ b/src/Security/Voter/ImpersonateUserVoter.php @@ -47,4 +47,14 @@ final class ImpersonateUserVoter extends Voter { return $this->helper->isGranted($token, 'users', 'impersonate'); } + + public function supportsAttribute(string $attribute): bool + { + return $attribute === 'CAN_SWITCH_USER'; + } + + public function supportsType(string $subjectType): bool + { + return is_a($subjectType, User::class, true); + } } \ No newline at end of file diff --git a/src/Security/Voter/LabelProfileVoter.php b/src/Security/Voter/LabelProfileVoter.php index 7f27d8d0..3d3f7f6f 100644 --- a/src/Security/Voter/LabelProfileVoter.php +++ b/src/Security/Voter/LabelProfileVoter.php @@ -78,4 +78,14 @@ final class LabelProfileVoter extends Voter return false; } + + public function supportsAttribute(string $attribute): bool + { + return isset(self::MAPPING[$attribute]); + } + + public function supportsType(string $subjectType): bool + { + return is_a($subjectType, LabelProfile::class, true); + } } diff --git a/src/Security/Voter/LogEntryVoter.php b/src/Security/Voter/LogEntryVoter.php index e60b66d1..6b6bbb42 100644 --- a/src/Security/Voter/LogEntryVoter.php +++ b/src/Security/Voter/LogEntryVoter.php @@ -85,4 +85,14 @@ final class LogEntryVoter extends Voter return false; } + + public function supportsAttribute(string $attribute): bool + { + return in_array($attribute, static::ALLOWED_OPS, true); + } + + public function supportsType(string $subjectType): bool + { + return is_a($subjectType, AbstractLogEntry::class, true); + } } diff --git a/src/Security/Voter/OrderdetailVoter.php b/src/Security/Voter/OrderdetailVoter.php index 625732b5..6c16cf42 100644 --- a/src/Security/Voter/OrderdetailVoter.php +++ b/src/Security/Voter/OrderdetailVoter.php @@ -90,4 +90,14 @@ final class OrderdetailVoter extends Voter return false; } + + public function supportsAttribute(string $attribute): bool + { + return in_array($attribute, self::ALLOWED_PERMS, true); + } + + public function supportsType(string $subjectType): bool + { + return $subjectType === 'string' || is_a($subjectType, Orderdetail::class, true); + } } diff --git a/src/Security/Voter/ParameterVoter.php b/src/Security/Voter/ParameterVoter.php index 96bd6060..47657055 100644 --- a/src/Security/Voter/ParameterVoter.php +++ b/src/Security/Voter/ParameterVoter.php @@ -47,6 +47,8 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter; final class ParameterVoter extends Voter { + private const ALLOWED_ATTRIBUTES = ['read', 'edit', 'delete', 'create', 'show_history', 'revert_element']; + public function __construct(private readonly Security $security, private readonly VoterHelper $helper) { } @@ -113,10 +115,21 @@ final class ParameterVoter extends Voter { if (is_a($subject, AbstractParameter::class, true)) { //These are the allowed attributes - return in_array($attribute, ['read', 'edit', 'delete', 'create', 'show_history', 'revert_element'], true); + return in_array($attribute, self::ALLOWED_ATTRIBUTES, true); } //Allow class name as subject return false; } + + public function supportsAttribute(string $attribute): bool + { + return in_array($attribute, self::ALLOWED_ATTRIBUTES, true); + } + + public function supportsType(string $subjectType): bool + { + return $subjectType === 'string' || is_a($subjectType, AbstractParameter::class, true); + } + } diff --git a/src/Security/Voter/PartLotVoter.php b/src/Security/Voter/PartLotVoter.php index 36fb230f..e4bfeddd 100644 --- a/src/Security/Voter/PartLotVoter.php +++ b/src/Security/Voter/PartLotVoter.php @@ -101,4 +101,14 @@ final class PartLotVoter extends Voter return false; } + + public function supportsAttribute(string $attribute): bool + { + return in_array($attribute, self::ALLOWED_PERMS, true); + } + + public function supportsType(string $subjectType): bool + { + return $subjectType === 'string' || is_a($subjectType, PartLot::class, true); + } } diff --git a/src/Security/Voter/PartVoter.php b/src/Security/Voter/PartVoter.php index c2c37563..88b67f8f 100644 --- a/src/Security/Voter/PartVoter.php +++ b/src/Security/Voter/PartVoter.php @@ -56,4 +56,14 @@ final class PartVoter extends Voter //Null concealing operator means, that no return $this->helper->isGranted($token, 'parts', $attribute); } + + public function supportsAttribute(string $attribute): bool + { + return $this->helper->isValidOperation('parts', $attribute); + } + + public function supportsType(string $subjectType): bool + { + return is_a($subjectType, Part::class, true); + } } diff --git a/src/Security/Voter/PermissionVoter.php b/src/Security/Voter/PermissionVoter.php index 01b34215..f6613cfc 100644 --- a/src/Security/Voter/PermissionVoter.php +++ b/src/Security/Voter/PermissionVoter.php @@ -47,6 +47,12 @@ final class PermissionVoter extends Voter return $this->helper->isGranted($token, $perm, $op); } + public function supportsAttribute(string $attribute): bool + { + //Check if the attribute has the form '@permission.operation' + return preg_match('#^@\\w+\\.\\w+$#', $attribute) === 1; + } + /** * Determines if the attribute and subject are supported by this voter. * diff --git a/src/Security/Voter/PricedetailVoter.php b/src/Security/Voter/PricedetailVoter.php index 9be5c4ed..3dc8fc41 100644 --- a/src/Security/Voter/PricedetailVoter.php +++ b/src/Security/Voter/PricedetailVoter.php @@ -87,4 +87,14 @@ final class PricedetailVoter extends Voter return false; } + + public function supportsType(string $subjectType): bool + { + return $subjectType === 'string' || is_a($subjectType, Pricedetail::class, true); + } + + public function supportsAttribute(string $attribute): bool + { + return in_array($attribute, self::ALLOWED_PERMS, true); + } } diff --git a/src/Security/Voter/StructureVoter.php b/src/Security/Voter/StructureVoter.php index 477bc28d..8f4c144f 100644 --- a/src/Security/Voter/StructureVoter.php +++ b/src/Security/Voter/StructureVoter.php @@ -76,6 +76,11 @@ final class StructureVoter extends Voter return false; } + public function supportsType(string $subjectType): bool + { + return $subjectType === 'string' || $this->instanceToPermissionName($subjectType) !== null; + } + /** * Maps an instance type to the permission name. * diff --git a/src/Security/Voter/UserVoter.php b/src/Security/Voter/UserVoter.php index f6502f6e..248e9444 100644 --- a/src/Security/Voter/UserVoter.php +++ b/src/Security/Voter/UserVoter.php @@ -60,6 +60,16 @@ final class UserVoter extends Voter return false; } + public function supportsAttribute(string $attribute): bool + { + return $this->helper->isValidOperation('users', $attribute) || $this->helper->isValidOperation('self', $attribute); + } + + public function supportsType(string $subjectType): bool + { + return $subjectType === 'string' || is_a($subjectType, User::class, true); + } + /** * Similar to voteOnAttribute, but checking for the anonymous user is already done. * The current user (or the anonymous user) is passed by $user. From 17b49e7ae59e65f95d4dc97796f50d33642babfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 28 Aug 2023 23:46:55 +0200 Subject: [PATCH 20/62] Added security to manufacturer and suppliers API endpoint --- src/Entity/Parts/Manufacturer.php | 18 ++++++++++++++++-- src/Entity/Parts/Supplier.php | 14 +++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/Entity/Parts/Manufacturer.php b/src/Entity/Parts/Manufacturer.php index 0eb9653a..7d5a0b4d 100644 --- a/src/Entity/Parts/Manufacturer.php +++ b/src/Entity/Parts/Manufacturer.php @@ -25,8 +25,12 @@ namespace App\Entity\Parts; use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; use ApiPlatform\Serializer\Filter\PropertyFilter; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentTypeAttachment; @@ -51,14 +55,24 @@ use Symfony\Component\Validator\Constraints as Assert; #[ORM\Index(name: 'manufacturer_name', columns: ['name'])] #[ORM\Index(name: 'manufacturer_idx_parent_name', columns: ['parent_id', 'name'])] #[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@manufacturers.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], normalizationContext: ['groups' => ['manufacturer:read', 'company:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], denormalizationContext: ['groups' => ['manufacturer:write', 'company:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], )] #[ApiResource( uriTemplate: '/manufacturers/{id}/children.{_format}', - operations: [new GetCollection(openapiContext: ['summary' => 'Retrieves the children elements of a manufacturer.'])], + operations: [ + new GetCollection(openapiContext: ['summary' => 'Retrieves the children elements of a manufacturer.'], + security: 'is_granted("@manufacturers.read")') + ], uriVariables: [ - 'id' => new Link(fromClass: Manufacturer::class, fromProperty: 'children') + 'id' => new Link(fromProperty: 'children', fromClass: Manufacturer::class) ], normalizationContext: ['groups' => ['manufacturer:read', 'company:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] )] diff --git a/src/Entity/Parts/Supplier.php b/src/Entity/Parts/Supplier.php index 94986435..fb0a731c 100644 --- a/src/Entity/Parts/Supplier.php +++ b/src/Entity/Parts/Supplier.php @@ -25,8 +25,12 @@ namespace App\Entity\Parts; use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; use ApiPlatform\Serializer\Filter\PropertyFilter; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentTypeAttachment; @@ -56,12 +60,20 @@ use Symfony\Component\Validator\Constraints as Assert; #[ORM\Index(name: 'supplier_idx_name', columns: ['name'])] #[ORM\Index(name: 'supplier_idx_parent_name', columns: ['parent_id', 'name'])] #[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@suppliers.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], normalizationContext: ['groups' => ['supplier:read', 'company:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], denormalizationContext: ['groups' => ['supplier:write', 'company:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], )] #[ApiResource( uriTemplate: '/suppliers/{id}/children.{_format}', - operations: [new GetCollection(openapiContext: ['summary' => 'Retrieves the children elements of a supplier'])], + operations: [new GetCollection(openapiContext: ['summary' => 'Retrieves the children elements of a supplier'], + security: 'is_granted("@manufacturers.read")')], uriVariables: [ 'id' => new Link(fromClass: Supplier::class, fromProperty: 'children') ], From 9bd1b86f6eb4c480079eb9500d0463dc16ee2b32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 3 Sep 2023 13:47:11 +0200 Subject: [PATCH 21/62] Updated dependencies --- composer.lock | 432 ++++++++++++++++++++++++++------------------------ yarn.lock | 405 +++++++++++++++++++++++++--------------------- 2 files changed, 443 insertions(+), 394 deletions(-) diff --git a/composer.lock b/composer.lock index bc340258..d432e6d2 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "api-platform/core", - "version": "v3.1.15", + "version": "v3.1.16", "source": { "type": "git", "url": "https://github.com/api-platform/core.git", - "reference": "0b51d280a101e9f8bd9cfe9c18968164d8e19375" + "reference": "9fa59e23f9000d6016aef00fad5b5cd72ac07919" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/core/zipball/0b51d280a101e9f8bd9cfe9c18968164d8e19375", - "reference": "0b51d280a101e9f8bd9cfe9c18968164d8e19375", + "url": "https://api.github.com/repos/api-platform/core/zipball/9fa59e23f9000d6016aef00fad5b5cd72ac07919", + "reference": "9fa59e23f9000d6016aef00fad5b5cd72ac07919", "shasum": "" }, "require": { @@ -165,7 +165,7 @@ ], "support": { "issues": "https://github.com/api-platform/core/issues", - "source": "https://github.com/api-platform/core/tree/v3.1.15" + "source": "https://github.com/api-platform/core/tree/v3.1.16" }, "funding": [ { @@ -173,7 +173,7 @@ "type": "tidelift" } ], - "time": "2023-08-25T08:54:22+00:00" + "time": "2023-09-02T07:06:13+00:00" }, { "name": "beberlei/assert", @@ -356,16 +356,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.3.6", + "version": "1.3.7", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "90d087e988ff194065333d16bc5cf649872d9cdb" + "reference": "76e46335014860eec1aa5a724799a00a2e47cc85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/90d087e988ff194065333d16bc5cf649872d9cdb", - "reference": "90d087e988ff194065333d16bc5cf649872d9cdb", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/76e46335014860eec1aa5a724799a00a2e47cc85", + "reference": "76e46335014860eec1aa5a724799a00a2e47cc85", "shasum": "" }, "require": { @@ -412,7 +412,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.3.6" + "source": "https://github.com/composer/ca-bundle/tree/1.3.7" }, "funding": [ { @@ -428,7 +428,7 @@ "type": "tidelift" } ], - "time": "2023-06-06T12:02:59+00:00" + "time": "2023-08-30T09:31:38+00:00" }, { "name": "composer/package-versions-deprecated", @@ -1726,16 +1726,16 @@ }, { "name": "doctrine/orm", - "version": "2.16.1", + "version": "2.16.2", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "597a63a86ca8c5f9d1ec2dc74fe3d1269d43434a" + "reference": "17500f56eaa930f5cd14d765bc2cd851c7d37cc0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/597a63a86ca8c5f9d1ec2dc74fe3d1269d43434a", - "reference": "597a63a86ca8c5f9d1ec2dc74fe3d1269d43434a", + "url": "https://api.github.com/repos/doctrine/orm/zipball/17500f56eaa930f5cd14d765bc2cd851c7d37cc0", + "reference": "17500f56eaa930f5cd14d765bc2cd851c7d37cc0", "shasum": "" }, "require": { @@ -1821,9 +1821,9 @@ ], "support": { "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/2.16.1" + "source": "https://github.com/doctrine/orm/tree/2.16.2" }, - "time": "2023-08-09T13:05:08+00:00" + "time": "2023-08-27T18:21:56+00:00" }, { "name": "doctrine/persistence", @@ -2038,7 +2038,7 @@ "issues": "https://github.com/dompdf/dompdf/issues", "source": "https://github.com/dompdf/dompdf/tree/master" }, - "time": "2023-06-23T12:41:01+00:00" + "time": "2023-09-01T22:55:58+00:00" }, { "name": "egulias/email-validator", @@ -2565,22 +2565,22 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.7.0", + "version": "7.8.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5" + "reference": "1110f66a6530a40fe7aea0378fe608ee2b2248f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/fb7566caccf22d74d1ab270de3551f72a58399f5", - "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/1110f66a6530a40fe7aea0378fe608ee2b2248f9", + "reference": "1110f66a6530a40fe7aea0378fe608ee2b2248f9", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0", - "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", + "guzzlehttp/promises": "^1.5.3 || ^2.0.1", + "guzzlehttp/psr7": "^1.9.1 || ^2.5.1", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -2671,7 +2671,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.7.0" + "source": "https://github.com/guzzle/guzzle/tree/7.8.0" }, "funding": [ { @@ -2687,7 +2687,7 @@ "type": "tidelift" } ], - "time": "2023-05-21T14:04:53+00:00" + "time": "2023-08-27T10:20:53+00:00" }, { "name": "guzzlehttp/promises", @@ -2774,16 +2774,16 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.6.0", + "version": "2.6.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "8bd7c33a0734ae1c5d074360512beb716bef3f77" + "reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/8bd7c33a0734ae1c5d074360512beb716bef3f77", - "reference": "8bd7c33a0734ae1c5d074360512beb716bef3f77", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/be45764272e8873c72dbe3d2edcfdfcc3bc9f727", + "reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727", "shasum": "" }, "require": { @@ -2870,7 +2870,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.6.0" + "source": "https://github.com/guzzle/psr7/tree/2.6.1" }, "funding": [ { @@ -2886,7 +2886,7 @@ "type": "tidelift" } ], - "time": "2023-08-03T15:06:02+00:00" + "time": "2023-08-27T10:13:57+00:00" }, { "name": "imagine/imagine", @@ -9798,16 +9798,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", "shasum": "" }, "require": { @@ -9822,7 +9822,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -9860,7 +9860,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" }, "funding": [ { @@ -9876,20 +9876,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354" + "reference": "875e90aeea2777b6f135677f618529449334a612" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612", + "reference": "875e90aeea2777b6f135677f618529449334a612", "shasum": "" }, "require": { @@ -9901,7 +9901,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -9941,7 +9941,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0" }, "funding": [ { @@ -9957,20 +9957,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-intl-icu", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-icu.git", - "reference": "a3d9148e2c363588e05abbdd4ee4f971f0a5330c" + "reference": "e46b4da57951a16053cd751f63f4a24292788157" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/a3d9148e2c363588e05abbdd4ee4f971f0a5330c", - "reference": "a3d9148e2c363588e05abbdd4ee4f971f0a5330c", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/e46b4da57951a16053cd751f63f4a24292788157", + "reference": "e46b4da57951a16053cd751f63f4a24292788157", "shasum": "" }, "require": { @@ -9982,7 +9982,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -10028,7 +10028,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.28.0" }, "funding": [ { @@ -10044,20 +10044,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-03-21T17:27:24+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "639084e360537a19f9ee352433b84ce831f3d2da" + "reference": "ecaafce9f77234a6a449d29e49267ba10499116d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/639084e360537a19f9ee352433b84ce831f3d2da", - "reference": "639084e360537a19f9ee352433b84ce831f3d2da", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/ecaafce9f77234a6a449d29e49267ba10499116d", + "reference": "ecaafce9f77234a6a449d29e49267ba10499116d", "shasum": "" }, "require": { @@ -10071,7 +10071,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -10115,7 +10115,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.28.0" }, "funding": [ { @@ -10131,20 +10131,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:30:37+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", "shasum": "" }, "require": { @@ -10156,7 +10156,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -10199,7 +10199,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0" }, "funding": [ { @@ -10215,20 +10215,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + "reference": "42292d99c55abe617799667f454222c54c60e229" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", + "reference": "42292d99c55abe617799667f454222c54c60e229", "shasum": "" }, "require": { @@ -10243,7 +10243,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -10282,7 +10282,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" }, "funding": [ { @@ -10298,20 +10298,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-07-28T09:04:16+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "869329b1e9894268a8a61dabb69153029b7a8c97" + "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/869329b1e9894268a8a61dabb69153029b7a8c97", - "reference": "869329b1e9894268a8a61dabb69153029b7a8c97", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/70f4aebd92afca2f865444d30a4d2151c13c3179", + "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179", "shasum": "" }, "require": { @@ -10320,7 +10320,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -10358,7 +10358,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php72/tree/v1.28.0" }, "funding": [ { @@ -10374,20 +10374,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", "shasum": "" }, "require": { @@ -10396,7 +10396,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -10441,7 +10441,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" }, "funding": [ { @@ -10457,20 +10457,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "508c652ba3ccf69f8c97f251534f229791b52a57" + "reference": "b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/508c652ba3ccf69f8c97f251534f229791b52a57", - "reference": "508c652ba3ccf69f8c97f251534f229791b52a57", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11", + "reference": "b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11", "shasum": "" }, "require": { @@ -10480,7 +10480,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -10493,7 +10493,10 @@ ], "psr-4": { "Symfony\\Polyfill\\Php83\\": "" - } + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -10518,7 +10521,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.28.0" }, "funding": [ { @@ -10534,20 +10537,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-08-16T06:22:46+00:00" }, { "name": "symfony/polyfill-uuid", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", - "reference": "f3cf1a645c2734236ed1e2e671e273eeb3586166" + "reference": "9c44518a5aff8da565c8a55dbe85d2769e6f630e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/f3cf1a645c2734236ed1e2e671e273eeb3586166", - "reference": "f3cf1a645c2734236ed1e2e671e273eeb3586166", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/9c44518a5aff8da565c8a55dbe85d2769e6f630e", + "reference": "9c44518a5aff8da565c8a55dbe85d2769e6f630e", "shasum": "" }, "require": { @@ -10562,7 +10565,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -10600,7 +10603,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.28.0" }, "funding": [ { @@ -10616,7 +10619,7 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/process", @@ -13285,16 +13288,16 @@ }, { "name": "twig/cssinliner-extra", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/twigphp/cssinliner-extra.git", - "reference": "85c8f3d7712bab57f6162f9637613df0511f207b" + "reference": "59d107afea4ca58be35ae1386bd90b53424f3b34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/cssinliner-extra/zipball/85c8f3d7712bab57f6162f9637613df0511f207b", - "reference": "85c8f3d7712bab57f6162f9637613df0511f207b", + "url": "https://api.github.com/repos/twigphp/cssinliner-extra/zipball/59d107afea4ca58be35ae1386bd90b53424f3b34", + "reference": "59d107afea4ca58be35ae1386bd90b53424f3b34", "shasum": "" }, "require": { @@ -13303,7 +13306,7 @@ "twig/twig": "^2.7|^3.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" + "symfony/phpunit-bridge": "^5.4|^6.3" }, "type": "library", "autoload": { @@ -13334,7 +13337,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/cssinliner-extra/tree/v3.7.0" + "source": "https://github.com/twigphp/cssinliner-extra/tree/v3.7.1" }, "funding": [ { @@ -13346,31 +13349,31 @@ "type": "tidelift" } ], - "time": "2023-02-09T06:45:16+00:00" + "time": "2023-07-29T15:34:56+00:00" }, { "name": "twig/extra-bundle", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/twigphp/twig-extra-bundle.git", - "reference": "802cc2dd46ec88285d6c7fa85c26ab7f2cd5bc49" + "reference": "f10baafe6eb0ecd615d52d5cbfb713a39f68e8f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/802cc2dd46ec88285d6c7fa85c26ab7f2cd5bc49", - "reference": "802cc2dd46ec88285d6c7fa85c26ab7f2cd5bc49", + "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/f10baafe6eb0ecd615d52d5cbfb713a39f68e8f3", + "reference": "f10baafe6eb0ecd615d52d5cbfb713a39f68e8f3", "shasum": "" }, "require": { "php": ">=7.2.5", - "symfony/framework-bundle": "^4.4|^5.0|^6.0", - "symfony/twig-bundle": "^4.4|^5.0|^6.0", + "symfony/framework-bundle": "^5.4|^6.0", + "symfony/twig-bundle": "^5.4|^6.0", "twig/twig": "^2.7|^3.0" }, "require-dev": { "league/commonmark": "^1.0|^2.0", - "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0", + "symfony/phpunit-bridge": "^5.4|^6.3", "twig/cache-extra": "^3.0", "twig/cssinliner-extra": "^2.12|^3.0", "twig/html-extra": "^2.12|^3.0", @@ -13408,7 +13411,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.7.0" + "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.7.1" }, "funding": [ { @@ -13420,29 +13423,29 @@ "type": "tidelift" } ], - "time": "2023-05-06T11:11:46+00:00" + "time": "2023-07-29T15:34:56+00:00" }, { "name": "twig/html-extra", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/twigphp/html-extra.git", - "reference": "af5b336a13122d28d405714b6f2abe840632251b" + "reference": "95ceb36e70fa8d07af08cf5135ecbf5e0bd8f386" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/html-extra/zipball/af5b336a13122d28d405714b6f2abe840632251b", - "reference": "af5b336a13122d28d405714b6f2abe840632251b", + "url": "https://api.github.com/repos/twigphp/html-extra/zipball/95ceb36e70fa8d07af08cf5135ecbf5e0bd8f386", + "reference": "95ceb36e70fa8d07af08cf5135ecbf5e0bd8f386", "shasum": "" }, "require": { "php": ">=7.1.3", - "symfony/mime": "^4.4|^5.0|^6.0", + "symfony/mime": "^5.4|^6.0", "twig/twig": "^2.7|^3.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" + "symfony/phpunit-bridge": "^5.4|^6.3" }, "type": "library", "autoload": { @@ -13472,7 +13475,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/html-extra/tree/v3.7.0" + "source": "https://github.com/twigphp/html-extra/tree/v3.7.1" }, "funding": [ { @@ -13484,20 +13487,20 @@ "type": "tidelift" } ], - "time": "2023-02-09T06:45:16+00:00" + "time": "2023-07-29T15:34:56+00:00" }, { "name": "twig/inky-extra", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/twigphp/inky-extra.git", - "reference": "907abf7046082cc151a3ee01f268dbf5f5f28eab" + "reference": "c6145677b63b3fcd27b1b3bbae9e64edbf12a273" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/inky-extra/zipball/907abf7046082cc151a3ee01f268dbf5f5f28eab", - "reference": "907abf7046082cc151a3ee01f268dbf5f5f28eab", + "url": "https://api.github.com/repos/twigphp/inky-extra/zipball/c6145677b63b3fcd27b1b3bbae9e64edbf12a273", + "reference": "c6145677b63b3fcd27b1b3bbae9e64edbf12a273", "shasum": "" }, "require": { @@ -13506,7 +13509,7 @@ "twig/twig": "^2.7|^3.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" + "symfony/phpunit-bridge": "^5.4|^6.3" }, "type": "library", "autoload": { @@ -13538,7 +13541,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/inky-extra/tree/v3.7.0" + "source": "https://github.com/twigphp/inky-extra/tree/v3.7.1" }, "funding": [ { @@ -13550,29 +13553,29 @@ "type": "tidelift" } ], - "time": "2023-02-09T06:45:16+00:00" + "time": "2023-07-29T15:34:56+00:00" }, { "name": "twig/intl-extra", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/twigphp/intl-extra.git", - "reference": "a97c323bebfca009d02994a5a8568c0b412a49ab" + "reference": "4f4fe572f635534649cc069e1dafe4a8ad63774d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/intl-extra/zipball/a97c323bebfca009d02994a5a8568c0b412a49ab", - "reference": "a97c323bebfca009d02994a5a8568c0b412a49ab", + "url": "https://api.github.com/repos/twigphp/intl-extra/zipball/4f4fe572f635534649cc069e1dafe4a8ad63774d", + "reference": "4f4fe572f635534649cc069e1dafe4a8ad63774d", "shasum": "" }, "require": { "php": ">=7.1.3", - "symfony/intl": "^4.4|^5.0|^6.0", + "symfony/intl": "^5.4|^6.0", "twig/twig": "^2.7|^3.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" + "symfony/phpunit-bridge": "^5.4|^6.3" }, "type": "library", "autoload": { @@ -13602,7 +13605,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/intl-extra/tree/v3.7.0" + "source": "https://github.com/twigphp/intl-extra/tree/v3.7.1" }, "funding": [ { @@ -13614,20 +13617,20 @@ "type": "tidelift" } ], - "time": "2023-02-09T06:45:16+00:00" + "time": "2023-07-29T15:34:56+00:00" }, { "name": "twig/markdown-extra", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/twigphp/markdown-extra.git", - "reference": "8f1179e279cea6ef14066a4560b859df58acd5d8" + "reference": "83dfa86a0379f784ea30bdb9c15a356b8aabf780" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/8f1179e279cea6ef14066a4560b859df58acd5d8", - "reference": "8f1179e279cea6ef14066a4560b859df58acd5d8", + "url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/83dfa86a0379f784ea30bdb9c15a356b8aabf780", + "reference": "83dfa86a0379f784ea30bdb9c15a356b8aabf780", "shasum": "" }, "require": { @@ -13639,7 +13642,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": "^4.4.9|^5.0.9|^6.0" + "symfony/phpunit-bridge": "^5.4|^6.3" }, "type": "library", "autoload": { @@ -13670,7 +13673,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/markdown-extra/tree/v3.7.0" + "source": "https://github.com/twigphp/markdown-extra/tree/v3.7.1" }, "funding": [ { @@ -13682,20 +13685,20 @@ "type": "tidelift" } ], - "time": "2023-02-09T06:45:16+00:00" + "time": "2023-07-29T15:34:56+00:00" }, { "name": "twig/twig", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "5cf942bbab3df42afa918caeba947f1b690af64b" + "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/5cf942bbab3df42afa918caeba947f1b690af64b", - "reference": "5cf942bbab3df42afa918caeba947f1b690af64b", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", + "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", "shasum": "" }, "require": { @@ -13705,7 +13708,7 @@ }, "require-dev": { "psr/container": "^1.0|^2.0", - "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" + "symfony/phpunit-bridge": "^5.4.9|^6.3" }, "type": "library", "autoload": { @@ -13741,7 +13744,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.7.0" + "source": "https://github.com/twigphp/Twig/tree/v3.7.1" }, "funding": [ { @@ -13753,7 +13756,7 @@ "type": "tidelift" } ], - "time": "2023-07-26T07:16:09+00:00" + "time": "2023-08-28T11:09:02+00:00" }, { "name": "ua-parser/uap-php", @@ -13903,16 +13906,16 @@ }, { "name": "web-auth/metadata-service", - "version": "4.6.4", + "version": "4.7.0", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-metadata-service.git", - "reference": "b58fbb0df46450acc426329bd87b60d794859da0" + "reference": "d38751afc45b20e880ddb89b4e4c879b56b5a0be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-metadata-service/zipball/b58fbb0df46450acc426329bd87b60d794859da0", - "reference": "b58fbb0df46450acc426329bd87b60d794859da0", + "url": "https://api.github.com/repos/web-auth/webauthn-metadata-service/zipball/d38751afc45b20e880ddb89b4e4c879b56b5a0be", + "reference": "d38751afc45b20e880ddb89b4e4c879b56b5a0be", "shasum": "" }, "require": { @@ -13968,7 +13971,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-metadata-service/tree/4.6.4" + "source": "https://github.com/web-auth/webauthn-metadata-service/tree/4.7.0" }, "funding": [ { @@ -13980,20 +13983,20 @@ "type": "patreon" } ], - "time": "2023-06-01T19:06:30+00:00" + "time": "2023-07-30T17:14:57+00:00" }, { "name": "web-auth/webauthn-lib", - "version": "4.6.4", + "version": "4.7.0", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-lib.git", - "reference": "8cb4949d81ef8414c82f334fb3514141aa013340" + "reference": "760dda5f64be89bc535120b054b44f349e495a79" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/8cb4949d81ef8414c82f334fb3514141aa013340", - "reference": "8cb4949d81ef8414c82f334fb3514141aa013340", + "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/760dda5f64be89bc535120b054b44f349e495a79", + "reference": "760dda5f64be89bc535120b054b44f349e495a79", "shasum": "" }, "require": { @@ -14008,7 +14011,7 @@ "psr/log": "^1.0|^2.0|^3.0", "spomky-labs/cbor-php": "^3.0", "symfony/uid": "^6.1", - "web-auth/cose-lib": "^4.0.12", + "web-auth/cose-lib": "^4.2.3", "web-auth/metadata-service": "self.version" }, "require-dev": { @@ -14056,7 +14059,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-lib/tree/4.6.4" + "source": "https://github.com/web-auth/webauthn-lib/tree/4.7.0" }, "funding": [ { @@ -14068,20 +14071,20 @@ "type": "patreon" } ], - "time": "2023-07-15T14:53:06+00:00" + "time": "2023-07-30T17:14:57+00:00" }, { "name": "web-auth/webauthn-symfony-bundle", - "version": "4.6.4", + "version": "4.7.0", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-symfony-bundle.git", - "reference": "04bd26182e26c8bf218bdca3d8b0f569ff983ff8" + "reference": "11961539c90ed1bb87fc0ff31a7e3be5d0bfc1ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-symfony-bundle/zipball/04bd26182e26c8bf218bdca3d8b0f569ff983ff8", - "reference": "04bd26182e26c8bf218bdca3d8b0f569ff983ff8", + "url": "https://api.github.com/repos/web-auth/webauthn-symfony-bundle/zipball/11961539c90ed1bb87fc0ff31a7e3be5d0bfc1ac", + "reference": "11961539c90ed1bb87fc0ff31a7e3be5d0bfc1ac", "shasum": "" }, "require": { @@ -14132,11 +14135,14 @@ "homepage": "https://github.com/web-auth", "keywords": [ "FIDO2", + "bundle", "fido", + "symfony", + "symfony-bundle", "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-symfony-bundle/tree/4.6.4" + "source": "https://github.com/web-auth/webauthn-symfony-bundle/tree/4.7.0" }, "funding": [ { @@ -14148,7 +14154,7 @@ "type": "patreon" } ], - "time": "2023-06-12T14:32:32+00:00" + "time": "2023-07-30T17:14:57+00:00" }, { "name": "web-token/jwt-core", @@ -14363,16 +14369,16 @@ }, { "name": "webonyx/graphql-php", - "version": "v15.6.2", + "version": "v15.6.3", "source": { "type": "git", "url": "https://github.com/webonyx/graphql-php.git", - "reference": "0a917058620a197530b357c46d8d5943a054a37e" + "reference": "3f045a4626d179ea6462e1a11744619860b55eb8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/0a917058620a197530b357c46d8d5943a054a37e", - "reference": "0a917058620a197530b357c46d8d5943a054a37e", + "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/3f045a4626d179ea6462e1a11744619860b55eb8", + "reference": "3f045a4626d179ea6462e1a11744619860b55eb8", "shasum": "" }, "require": { @@ -14389,14 +14395,14 @@ "nyholm/psr7": "^1.5", "phpbench/phpbench": "^1.2", "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "1.10.28", - "phpstan/phpstan-phpunit": "1.3.13", + "phpstan/phpstan": "1.10.32", + "phpstan/phpstan-phpunit": "1.3.14", "phpstan/phpstan-strict-rules": "1.5.1", "phpunit/phpunit": "^9.5 || ^10", "psr/http-message": "^1 || ^2", "react/http": "^1.6", "react/promise": "^2.9", - "rector/rector": "^0.17", + "rector/rector": "^0.18", "symfony/polyfill-php81": "^1.23", "symfony/var-exporter": "^5 || ^6", "thecodingmachine/safe": "^1.3 || ^2" @@ -14424,7 +14430,7 @@ ], "support": { "issues": "https://github.com/webonyx/graphql-php/issues", - "source": "https://github.com/webonyx/graphql-php/tree/v15.6.2" + "source": "https://github.com/webonyx/graphql-php/tree/v15.6.3" }, "funding": [ { @@ -14432,7 +14438,7 @@ "type": "open_collective" } ], - "time": "2023-08-08T17:15:12+00:00" + "time": "2023-09-01T08:13:33+00:00" }, { "name": "willdurand/negotiation", @@ -14731,16 +14737,16 @@ }, { "name": "composer/semver", - "version": "3.3.2", + "version": "3.4.0", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", + "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", "shasum": "" }, "require": { @@ -14790,9 +14796,9 @@ "versioning" ], "support": { - "irc": "irc://irc.freenode.org/composer", + "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.3.2" + "source": "https://github.com/composer/semver/tree/3.4.0" }, "funding": [ { @@ -14808,7 +14814,7 @@ "type": "tidelift" } ], - "time": "2022-04-01T19:23:25+00:00" + "time": "2023-08-31T09:50:34+00:00" }, { "name": "composer/xdebug-handler", @@ -15449,16 +15455,16 @@ }, { "name": "phpstan/phpstan-doctrine", - "version": "1.3.42", + "version": "1.3.43", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-doctrine.git", - "reference": "e4678fa1055bfd7fad052506b422aeae35fc6f63" + "reference": "c5015035755ad2d5013bd6bf98ff423ca6150822" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/e4678fa1055bfd7fad052506b422aeae35fc6f63", - "reference": "e4678fa1055bfd7fad052506b422aeae35fc6f63", + "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/c5015035755ad2d5013bd6bf98ff423ca6150822", + "reference": "c5015035755ad2d5013bd6bf98ff423ca6150822", "shasum": "" }, "require": { @@ -15513,9 +15519,9 @@ "description": "Doctrine extensions for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-doctrine/issues", - "source": "https://github.com/phpstan/phpstan-doctrine/tree/1.3.42" + "source": "https://github.com/phpstan/phpstan-doctrine/tree/1.3.43" }, - "time": "2023-08-09T08:21:24+00:00" + "time": "2023-09-01T15:01:13+00:00" }, { "name": "phpstan/phpstan-strict-rules", @@ -15704,21 +15710,21 @@ }, { "name": "rector/rector", - "version": "0.18.0", + "version": "0.18.1", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "758ada29b5c80d933f906735d3026520390a2a1d" + "reference": "ee72ef542680a7f47ed8c6784f78b032c0d2f381" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/758ada29b5c80d933f906735d3026520390a2a1d", - "reference": "758ada29b5c80d933f906735d3026520390a2a1d", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/ee72ef542680a7f47ed8c6784f78b032c0d2f381", + "reference": "ee72ef542680a7f47ed8c6784f78b032c0d2f381", "shasum": "" }, "require": { "php": "^7.2|^8.0", - "phpstan/phpstan": "^1.10.26" + "phpstan/phpstan": "^1.10.31" }, "conflict": { "rector/rector-doctrine": "*", @@ -15748,7 +15754,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/0.18.0" + "source": "https://github.com/rectorphp/rector/tree/0.18.1" }, "funding": [ { @@ -15756,7 +15762,7 @@ "type": "github" } ], - "time": "2023-08-17T12:53:22+00:00" + "time": "2023-08-28T18:01:58+00:00" }, { "name": "roave/security-advisories", @@ -15764,12 +15770,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "9160fb2612003a99a28abbd588519d5ab3a77024" + "reference": "9971379442eb64596a59f6c3b89ec66fac58c611" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/9160fb2612003a99a28abbd588519d5ab3a77024", - "reference": "9160fb2612003a99a28abbd588519d5ab3a77024", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/9971379442eb64596a59f6c3b89ec66fac58c611", + "reference": "9971379442eb64596a59f6c3b89ec66fac58c611", "shasum": "" }, "conflict": { @@ -15789,6 +15795,7 @@ "anchorcms/anchor-cms": "<=0.12.7", "andreapollastri/cipi": "<=3.1.15", "andrewhaine/silverstripe-form-capture": ">=0.2,<=0.2.3|>=1,<1.0.2|>=2,<2.2.5", + "apache-solr-for-typo3/solr": "<2.8.3", "apereo/phpcas": "<1.6", "api-platform/core": ">=2.2,<2.2.10|>=2.3,<2.3.6|>=2.6,<2.7.10|>=3,<3.0.12|>=3.1,<3.1.3", "appwrite/server-ce": "<=1.2.1", @@ -15838,6 +15845,7 @@ "centreon/centreon": "<22.10.0.0-beta1", "cesnet/simplesamlphp-module-proxystatistics": "<3.1", "chriskacerguis/codeigniter-restserver": "<=2.7.1", + "civicrm/civicrm-core": ">=4.2,<4.2.9|>=4.3,<4.3.3", "cockpit-hq/cockpit": "<=2.6.3", "codeception/codeception": "<3.1.3|>=4,<4.1.22", "codeigniter/framework": "<3.1.9", @@ -15935,6 +15943,7 @@ "friendsofsymfony/rest-bundle": ">=1.2,<1.2.2", "friendsofsymfony/user-bundle": ">=1.2,<1.3.5", "friendsoftypo3/mediace": ">=7.6.2,<7.6.5", + "friendsoftypo3/openid": ">=4.5,<4.5.31|>=4.7,<4.7.16|>=6,<6.0.11|>=6.1,<6.1.6", "froala/wysiwyg-editor": "<3.2.7", "froxlor/froxlor": "<2.1", "fuel/core": "<1.8.1", @@ -15989,6 +15998,7 @@ "ivankristianto/phpwhois": "<=4.3", "jackalope/jackalope-doctrine-dbal": "<1.7.4", "james-heinrich/getid3": "<1.9.21", + "james-heinrich/phpthumb": "<1.7.12", "jasig/phpcas": "<1.3.3", "jcbrand/converse.js": "<3.3.3", "joomla/archive": "<1.1.12|>=2,<2.0.1", @@ -16056,6 +16066,7 @@ "mobiledetect/mobiledetectlib": "<2.8.32", "modx/revolution": "<=2.8.3.0-patch", "mojo42/jirafeau": "<4.4", + "mongodb/mongodb": ">=1,<1.9.2", "monolog/monolog": ">=1.8,<1.12", "moodle/moodle": "<4.2.0.0-RC2-dev|==4.2", "movim/moxl": ">=0.8,<=0.10", @@ -16214,7 +16225,7 @@ "socalnick/scn-social-auth": "<1.15.2", "socialiteproviders/steam": "<1.1", "spatie/browsershot": "<3.57.4", - "spipu/html2pdf": "<5.2.4", + "spipu/html2pdf": "<5.2.8", "spoon/library": "<1.4.1", "spoonity/tcpdf": "<6.2.22", "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", @@ -16305,6 +16316,7 @@ "typo3/swiftmailer": ">=4.1,<4.1.99|>=5.4,<5.4.5", "typo3fluid/fluid": ">=2,<2.0.8|>=2.1,<2.1.7|>=2.2,<2.2.4|>=2.3,<2.3.7|>=2.4,<2.4.4|>=2.5,<2.5.11|>=2.6,<2.6.10", "ua-parser/uap-php": "<3.8", + "uasoft-indonesia/badaso": "<=2.9.7", "unisharp/laravel-filemanager": "<=2.5.1", "userfrosting/userfrosting": ">=0.3.1,<4.6.3", "usmanhalalit/pixie": "<1.0.3|>=2,<2.0.2", @@ -16416,7 +16428,7 @@ "type": "tidelift" } ], - "time": "2023-08-23T09:04:12+00:00" + "time": "2023-08-31T19:04:14+00:00" }, { "name": "sebastian/diff", diff --git a/yarn.lock b/yarn.lock index bc5f5b7f..2e4f2616 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11,37 +11,37 @@ "@jridgewell/trace-mapping" "^0.3.9" "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.22.10", "@babel/code-frame@^7.22.5": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.10.tgz#1c20e612b768fefa75f6e90d6ecb86329247f0a3" - integrity sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA== + version "7.22.13" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" + integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== dependencies: - "@babel/highlight" "^7.22.10" + "@babel/highlight" "^7.22.13" chalk "^2.4.2" -"@babel/compat-data@^7.22.5", "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9": +"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9": version "7.22.9" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730" integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ== "@babel/core@^7.19.6": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.10.tgz#aad442c7bcd1582252cb4576747ace35bc122f35" - integrity sha512-fTmqbbUBAwCcre6zPzNngvsI0aNrPZe77AeqvDxWM9Nm+04RrJ3CAmGHA9f7lJQY6ZMhRztNemy4uslDxTX4Qw== + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.11.tgz#8033acaa2aa24c3f814edaaa057f3ce0ba559c24" + integrity sha512-lh7RJrtPdhibbxndr6/xx0w8+CVlY5FJZiaSz908Fpy+G0xkBFTvwLcKJFF4PJxVfGhVWNebikpWGnOoC71juQ== dependencies: "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.22.10" "@babel/generator" "^7.22.10" "@babel/helper-compilation-targets" "^7.22.10" "@babel/helper-module-transforms" "^7.22.9" - "@babel/helpers" "^7.22.10" - "@babel/parser" "^7.22.10" + "@babel/helpers" "^7.22.11" + "@babel/parser" "^7.22.11" "@babel/template" "^7.22.5" - "@babel/traverse" "^7.22.10" - "@babel/types" "^7.22.10" + "@babel/traverse" "^7.22.11" + "@babel/types" "^7.22.11" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" - json5 "^2.2.2" + json5 "^2.2.3" semver "^6.3.1" "@babel/generator@^7.22.10": @@ -79,10 +79,10 @@ lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.22.5": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.10.tgz#dd2612d59eac45588021ac3d6fa976d08f4e95a3" - integrity sha512-5IBb77txKYQPpOEdUdIhBx8VrZyDCQ+H82H0+5dX1TmuscP5vJKEE3cKurjtIw/vFwzbVH48VweE78kVDBrqjA== +"@babel/helper-create-class-features-plugin@^7.22.11", "@babel/helper-create-class-features-plugin@^7.22.5": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.11.tgz#4078686740459eeb4af3494a273ac09148dfb213" + integrity sha512-y1grdYL4WzmUDBRGK0pDbIoFd7UZKoDurDzWEoNMYoj1EL+foGRQNyPWDcC+YyegN5y1DUsFFmzjGijB3nSVAQ== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" "@babel/helper-environment-visitor" "^7.22.5" @@ -234,28 +234,28 @@ "@babel/template" "^7.22.5" "@babel/types" "^7.22.10" -"@babel/helpers@^7.22.10": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.10.tgz#ae6005c539dfbcb5cd71fb51bfc8a52ba63bc37a" - integrity sha512-a41J4NW8HyZa1I1vAndrraTlPZ/eZoga2ZgS7fEr0tZJGVU4xqdE80CEm0CcNjha5EZ8fTBYLKHF0kqDUuAwQw== +"@babel/helpers@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.11.tgz#b02f5d5f2d7abc21ab59eeed80de410ba70b056a" + integrity sha512-vyOXC8PBWaGc5h7GMsNx68OH33cypkEDJCHvYVVgVbbxJDROYVtexSk0gK5iCF1xNjRIN2s8ai7hwkWDq5szWg== dependencies: "@babel/template" "^7.22.5" - "@babel/traverse" "^7.22.10" - "@babel/types" "^7.22.10" + "@babel/traverse" "^7.22.11" + "@babel/types" "^7.22.11" -"@babel/highlight@^7.22.10": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.10.tgz#02a3f6d8c1cb4521b2fd0ab0da8f4739936137d7" - integrity sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ== +"@babel/highlight@^7.22.13": + version "7.22.13" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.13.tgz#9cda839e5d3be9ca9e8c26b6dd69e7548f0cbf16" + integrity sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ== dependencies: "@babel/helper-validator-identifier" "^7.22.5" chalk "^2.4.2" js-tokens "^4.0.0" -"@babel/parser@^7.18.9", "@babel/parser@^7.22.10", "@babel/parser@^7.22.5": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.10.tgz#e37634f9a12a1716136c44624ef54283cabd3f55" - integrity sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ== +"@babel/parser@^7.18.9", "@babel/parser@^7.22.11", "@babel/parser@^7.22.5": + version "7.22.14" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.14.tgz#c7de58e8de106e88efca42ce17f0033209dfd245" + integrity sha512-1KucTHgOvaw/LzCVrEOAyXkr9rQlp0A1HiHRYnSUE9dmb8PvPW7o5sscg+5169r54n3vGlbx6GevTE/Iw/P3AQ== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.5": version "7.22.5" @@ -412,10 +412,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-async-generator-functions@^7.22.10": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.10.tgz#45946cd17f915b10e65c29b8ed18a0a50fc648c8" - integrity sha512-eueE8lvKVzq5wIObKK/7dvoeKJ+xc6TvRn6aysIjS6pSCeLy7S/eVi7pEQknZqyqvzaNKdDtem8nUNTBgDVR2g== +"@babel/plugin-transform-async-generator-functions@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.11.tgz#dbe3b1ff5a52e2e5edc4b19a60d325a675ed2649" + integrity sha512-0pAlmeRJn6wU84zzZsEOx1JV1Jf8fqO9ok7wofIJwUnplYo247dcd24P+cMJht7ts9xkzdtB0EPHmOb7F+KzXw== dependencies: "@babel/helper-environment-visitor" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" @@ -453,12 +453,12 @@ "@babel/helper-create-class-features-plugin" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-class-static-block@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz#3e40c46f048403472d6f4183116d5e46b1bff5ba" - integrity sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA== +"@babel/plugin-transform-class-static-block@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.11.tgz#dc8cc6e498f55692ac6b4b89e56d87cec766c974" + integrity sha512-GMM8gGmqI7guS/llMFk1bJDkKfn3v3C4KHK9Yg1ey5qcHcOlKb0QvcMrgzvxo+T03/4szNh5lghY+fEC98Kq9g== dependencies: - "@babel/helper-create-class-features-plugin" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.22.11" "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-class-static-block" "^7.14.5" @@ -507,10 +507,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-dynamic-import@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz#d6908a8916a810468c4edff73b5b75bda6ad393e" - integrity sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ== +"@babel/plugin-transform-dynamic-import@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.11.tgz#2c7722d2a5c01839eaf31518c6ff96d408e447aa" + integrity sha512-g/21plo58sfteWjaO0ZNVb+uEOkJNjAaHhbejrnBmu011l/eNDScmkbjCC3l4FKb10ViaGU4aOkFznSu2zRHgA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-dynamic-import" "^7.8.3" @@ -523,10 +523,10 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-export-namespace-from@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz#57c41cb1d0613d22f548fddd8b288eedb9973a5b" - integrity sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg== +"@babel/plugin-transform-export-namespace-from@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.11.tgz#b3c84c8f19880b6c7440108f8929caf6056db26c" + integrity sha512-xa7aad7q7OiT8oNZ1mU7NrISjlSkVdMbNxn9IuLZyL9AJEhs1Apba3I+u5riX1dIkdptP5EKDG5XDPByWxtehw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" @@ -547,10 +547,10 @@ "@babel/helper-function-name" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-json-strings@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz#14b64352fdf7e1f737eed68de1a1468bd2a77ec0" - integrity sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A== +"@babel/plugin-transform-json-strings@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.11.tgz#689a34e1eed1928a40954e37f74509f48af67835" + integrity sha512-CxT5tCqpA9/jXFlme9xIBCc5RPtdDq3JpkkhgHQqtDdiTnTI0jtZ0QzXhr5DILeYifDPp2wvY2ad+7+hLMW5Pw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-json-strings" "^7.8.3" @@ -562,10 +562,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-logical-assignment-operators@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz#66ae5f068fd5a9a5dc570df16f56c2a8462a9d6c" - integrity sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA== +"@babel/plugin-transform-logical-assignment-operators@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.11.tgz#24c522a61688bde045b7d9bc3c2597a4d948fc9c" + integrity sha512-qQwRTP4+6xFCDV5k7gZBF3C31K34ut0tbEcTKxlX/0KXxm9GLcO14p570aWxFvVzx6QAfPgq7gaeIHXJC8LswQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" @@ -585,22 +585,22 @@ "@babel/helper-module-transforms" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-modules-commonjs@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz#7d9875908d19b8c0536085af7b053fd5bd651bfa" - integrity sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA== +"@babel/plugin-transform-modules-commonjs@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.11.tgz#d7991d3abad199c03b68ee66a64f216c47ffdfae" + integrity sha512-o2+bg7GDS60cJMgz9jWqRUsWkMzLCxp+jFDeDUT5sjRlAxcJWZ2ylNdI7QQ2+CH5hWu7OnN+Cv3htt7AkSf96g== dependencies: - "@babel/helper-module-transforms" "^7.22.5" + "@babel/helper-module-transforms" "^7.22.9" "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-simple-access" "^7.22.5" -"@babel/plugin-transform-modules-systemjs@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz#18c31410b5e579a0092638f95c896c2a98a5d496" - integrity sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ== +"@babel/plugin-transform-modules-systemjs@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.11.tgz#3386be5875d316493b517207e8f1931d93154bb1" + integrity sha512-rIqHmHoMEOhI3VkVf5jQ15l539KrwhzqcBO6wdCNWPWc/JWt9ILNYNUssbRpeq0qWns8svuw8LnMNCvWBIJ8wA== dependencies: "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-module-transforms" "^7.22.5" + "@babel/helper-module-transforms" "^7.22.9" "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-validator-identifier" "^7.22.5" @@ -627,29 +627,29 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-nullish-coalescing-operator@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz#f8872c65776e0b552e0849d7596cddd416c3e381" - integrity sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA== +"@babel/plugin-transform-nullish-coalescing-operator@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.11.tgz#debef6c8ba795f5ac67cd861a81b744c5d38d9fc" + integrity sha512-YZWOw4HxXrotb5xsjMJUDlLgcDXSfO9eCmdl1bgW4+/lAGdkjaEvOnQ4p5WKKdUgSzO39dgPl0pTnfxm0OAXcg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" -"@babel/plugin-transform-numeric-separator@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz#57226a2ed9e512b9b446517ab6fa2d17abb83f58" - integrity sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g== +"@babel/plugin-transform-numeric-separator@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.11.tgz#498d77dc45a6c6db74bb829c02a01c1d719cbfbd" + integrity sha512-3dzU4QGPsILdJbASKhF/V2TVP+gJya1PsueQCxIPCEcerqF21oEcrob4mzjsp2Py/1nLfF5m+xYNMDpmA8vffg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-transform-object-rest-spread@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz#9686dc3447df4753b0b2a2fae7e8bc33cdc1f2e1" - integrity sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ== +"@babel/plugin-transform-object-rest-spread@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.11.tgz#dbbb06ce783cd994a8f430d8cefa553e9b42ca62" + integrity sha512-nX8cPFa6+UmbepISvlf5jhQyaC7ASs/7UxHmMkuJ/k5xSHvDPPaibMo+v3TXwU/Pjqhep/nFNpd3zn4YR59pnw== dependencies: - "@babel/compat-data" "^7.22.5" - "@babel/helper-compilation-targets" "^7.22.5" + "@babel/compat-data" "^7.22.9" + "@babel/helper-compilation-targets" "^7.22.10" "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-transform-parameters" "^7.22.5" @@ -662,18 +662,18 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-replace-supers" "^7.22.5" -"@babel/plugin-transform-optional-catch-binding@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz#842080be3076703be0eaf32ead6ac8174edee333" - integrity sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg== +"@babel/plugin-transform-optional-catch-binding@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.11.tgz#461cc4f578a127bb055527b3e77404cad38c08e0" + integrity sha512-rli0WxesXUeCJnMYhzAglEjLWVDF6ahb45HuprcmQuLidBJFWjNnOzssk2kuc6e33FlLaiZhG/kUIzUMWdBKaQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-transform-optional-chaining@^7.22.10", "@babel/plugin-transform-optional-chaining@^7.22.5": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.10.tgz#076d28a7e074392e840d4ae587d83445bac0372a" - integrity sha512-MMkQqZAZ+MGj+jGTG3OTuhKeBpNcO+0oCEbrGNEaOmiEn+1MzRyQlYsruGiU8RTK3zV6XwrVJTmwiDOyYK6J9g== +"@babel/plugin-transform-optional-chaining@^7.22.12", "@babel/plugin-transform-optional-chaining@^7.22.5": + version "7.22.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.12.tgz#d7ebf6a88cd2f4d307b0e000ab630acd8124b333" + integrity sha512-7XXCVqZtyFWqjDsYDY4T45w4mlx1rf7aOgkc/Ww76xkgBiOlmjPkx36PBLHa1k1rwWvVgYMPsbuVnIamx2ZQJw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" @@ -694,13 +694,13 @@ "@babel/helper-create-class-features-plugin" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-private-property-in-object@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz#07a77f28cbb251546a43d175a1dda4cf3ef83e32" - integrity sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ== +"@babel/plugin-transform-private-property-in-object@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.11.tgz#ad45c4fc440e9cb84c718ed0906d96cf40f9a4e1" + integrity sha512-sSCbqZDBKHetvjSwpyWzhuHkmW5RummxJBVbYLkGkaiTOWGxml7SXt0iWa03bzxFIx7wOj3g/ILRd0RcJKBeSQ== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-create-class-features-plugin" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.22.11" "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" @@ -794,9 +794,9 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/preset-env@^7.19.4": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.10.tgz#3263b9fe2c8823d191d28e61eac60a79f9ce8a0f" - integrity sha512-riHpLb1drNkpLlocmSyEg4oYJIQFeXAK/d7rI6mbD0XsvoTOOweXDmQPG/ErxsEhWk3rl3Q/3F6RFQlVFS8m0A== + version "7.22.14" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.14.tgz#1cbb468d899f64fa71c53446f13b7ff8c0005cc1" + integrity sha512-daodMIoVo+ol/g+//c/AH+szBkFj4STQUikvBijRGL72Ph+w+AMTSh55DUETe8KJlPlDT1k/mp7NBfOuiWmoig== dependencies: "@babel/compat-data" "^7.22.9" "@babel/helper-compilation-targets" "^7.22.10" @@ -824,41 +824,41 @@ "@babel/plugin-syntax-top-level-await" "^7.14.5" "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" "@babel/plugin-transform-arrow-functions" "^7.22.5" - "@babel/plugin-transform-async-generator-functions" "^7.22.10" + "@babel/plugin-transform-async-generator-functions" "^7.22.11" "@babel/plugin-transform-async-to-generator" "^7.22.5" "@babel/plugin-transform-block-scoped-functions" "^7.22.5" "@babel/plugin-transform-block-scoping" "^7.22.10" "@babel/plugin-transform-class-properties" "^7.22.5" - "@babel/plugin-transform-class-static-block" "^7.22.5" + "@babel/plugin-transform-class-static-block" "^7.22.11" "@babel/plugin-transform-classes" "^7.22.6" "@babel/plugin-transform-computed-properties" "^7.22.5" "@babel/plugin-transform-destructuring" "^7.22.10" "@babel/plugin-transform-dotall-regex" "^7.22.5" "@babel/plugin-transform-duplicate-keys" "^7.22.5" - "@babel/plugin-transform-dynamic-import" "^7.22.5" + "@babel/plugin-transform-dynamic-import" "^7.22.11" "@babel/plugin-transform-exponentiation-operator" "^7.22.5" - "@babel/plugin-transform-export-namespace-from" "^7.22.5" + "@babel/plugin-transform-export-namespace-from" "^7.22.11" "@babel/plugin-transform-for-of" "^7.22.5" "@babel/plugin-transform-function-name" "^7.22.5" - "@babel/plugin-transform-json-strings" "^7.22.5" + "@babel/plugin-transform-json-strings" "^7.22.11" "@babel/plugin-transform-literals" "^7.22.5" - "@babel/plugin-transform-logical-assignment-operators" "^7.22.5" + "@babel/plugin-transform-logical-assignment-operators" "^7.22.11" "@babel/plugin-transform-member-expression-literals" "^7.22.5" "@babel/plugin-transform-modules-amd" "^7.22.5" - "@babel/plugin-transform-modules-commonjs" "^7.22.5" - "@babel/plugin-transform-modules-systemjs" "^7.22.5" + "@babel/plugin-transform-modules-commonjs" "^7.22.11" + "@babel/plugin-transform-modules-systemjs" "^7.22.11" "@babel/plugin-transform-modules-umd" "^7.22.5" "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.5" "@babel/plugin-transform-new-target" "^7.22.5" - "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.5" - "@babel/plugin-transform-numeric-separator" "^7.22.5" - "@babel/plugin-transform-object-rest-spread" "^7.22.5" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.11" + "@babel/plugin-transform-numeric-separator" "^7.22.11" + "@babel/plugin-transform-object-rest-spread" "^7.22.11" "@babel/plugin-transform-object-super" "^7.22.5" - "@babel/plugin-transform-optional-catch-binding" "^7.22.5" - "@babel/plugin-transform-optional-chaining" "^7.22.10" + "@babel/plugin-transform-optional-catch-binding" "^7.22.11" + "@babel/plugin-transform-optional-chaining" "^7.22.12" "@babel/plugin-transform-parameters" "^7.22.5" "@babel/plugin-transform-private-methods" "^7.22.5" - "@babel/plugin-transform-private-property-in-object" "^7.22.5" + "@babel/plugin-transform-private-property-in-object" "^7.22.11" "@babel/plugin-transform-property-literals" "^7.22.5" "@babel/plugin-transform-regenerator" "^7.22.10" "@babel/plugin-transform-reserved-words" "^7.22.5" @@ -872,7 +872,7 @@ "@babel/plugin-transform-unicode-regex" "^7.22.5" "@babel/plugin-transform-unicode-sets-regex" "^7.22.5" "@babel/preset-modules" "0.1.6-no-external-plugins" - "@babel/types" "^7.22.10" + "@babel/types" "^7.22.11" babel-plugin-polyfill-corejs2 "^0.4.5" babel-plugin-polyfill-corejs3 "^0.8.3" babel-plugin-polyfill-regenerator "^0.5.2" @@ -894,9 +894,9 @@ integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== "@babel/runtime@^7.8.4": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.10.tgz#ae3e9631fd947cb7e3610d3e9d8fef5f76696682" - integrity sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ== + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.11.tgz#7a9ba3bbe406ad6f9e8dd4da2ece453eb23a77a4" + integrity sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA== dependencies: regenerator-runtime "^0.14.0" @@ -909,10 +909,10 @@ "@babel/parser" "^7.22.5" "@babel/types" "^7.22.5" -"@babel/traverse@^7.18.9", "@babel/traverse@^7.22.10": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.10.tgz#20252acb240e746d27c2e82b4484f199cf8141aa" - integrity sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig== +"@babel/traverse@^7.18.9", "@babel/traverse@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.11.tgz#71ebb3af7a05ff97280b83f05f8865ac94b2027c" + integrity sha512-mzAenteTfomcB7mfPtyi+4oe5BZ6MXxWcn4CX+h4IRJ+OOGXBrWU6jDQavkQI9Vuc5P+donFabBfFCcmWka9lQ== dependencies: "@babel/code-frame" "^7.22.10" "@babel/generator" "^7.22.10" @@ -920,15 +920,15 @@ "@babel/helper-function-name" "^7.22.5" "@babel/helper-hoist-variables" "^7.22.5" "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.22.10" - "@babel/types" "^7.22.10" + "@babel/parser" "^7.22.11" + "@babel/types" "^7.22.11" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.22.10", "@babel/types@^7.22.5", "@babel/types@^7.4.4": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.10.tgz#4a9e76446048f2c66982d1a989dd12b8a2d2dc03" - integrity sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg== +"@babel/types@^7.22.10", "@babel/types@^7.22.11", "@babel/types@^7.22.5", "@babel/types@^7.4.4": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.11.tgz#0e65a6a1d4d9cbaa892b2213f6159485fe632ea2" + integrity sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg== dependencies: "@babel/helper-string-parser" "^7.22.5" "@babel/helper-validator-identifier" "^7.22.5" @@ -990,10 +990,10 @@ "@ckeditor/ckeditor5-utils" "39.0.1" lodash-es "4.17.21" -"@ckeditor/ckeditor5-dev-translations@^38.4.0": - version "38.4.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-dev-translations/-/ckeditor5-dev-translations-38.4.0.tgz#1e58f9bd5a4454c519a516e631002cb2adb4a306" - integrity sha512-CyTCK9TuGjAQ3cDyckI0zlB1y8CL1ROAZVtVGkjRDRO7FpYX28lKNtEl5Old9829/LRnQtGwDMgJ1JzGVaeR2A== +"@ckeditor/ckeditor5-dev-translations@^38.4.0", "@ckeditor/ckeditor5-dev-translations@^38.4.1": + version "38.4.1" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-dev-translations/-/ckeditor5-dev-translations-38.4.1.tgz#ce56fd711dfb4e4a2143ef48efe50f1a77b94b6f" + integrity sha512-ldIbAWp4TvPOMPlR8cIQOfX1FOF/KmlHha2CXI6RtKLlgDhsDvTEprLgzcZhbKx1Su+9XfDytgz+Ag/UVXTfNQ== dependencies: "@babel/parser" "^7.18.9" "@babel/traverse" "^7.18.9" @@ -1003,11 +1003,11 @@ webpack-sources "^2.0.1" "@ckeditor/ckeditor5-dev-utils@^38.4.0": - version "38.4.0" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-dev-utils/-/ckeditor5-dev-utils-38.4.0.tgz#9b7f4f5ac7b57db2370b26f4a498a85260329c3f" - integrity sha512-z4wTMwzE+glTnbBQU7zNF0YiUe1qCXyJWuFwYIrCznAObiKzwZMVAcIfRtIvHJ4cCLQpaUSlS7rJxWvh/rbPcQ== + version "38.4.1" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-dev-utils/-/ckeditor5-dev-utils-38.4.1.tgz#6c8c721dbc7691ed5daa7b50615be21312d70d93" + integrity sha512-okhWneTHEbp/4ESYKqqD7EQFYpCOVOI2o/qJNF3Cnv7KPw1Fl2glP8jfZ6fYalTq3oL+vLCQW4bWjhcKdSJkog== dependencies: - "@ckeditor/ckeditor5-dev-translations" "^38.4.0" + "@ckeditor/ckeditor5-dev-translations" "^38.4.1" chalk "^3.0.0" cli-cursor "^3.1.0" cli-spinners "^2.6.1" @@ -1853,9 +1853,9 @@ integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== "@types/node@*": - version "20.5.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.5.3.tgz#fa52c147f405d56b2f1dd8780d840aa87ddff629" - integrity sha512-ITI7rbWczR8a/S6qjAW7DMqxqFMjjTo61qZVWJ1ubPvbIQsL5D/TvwjYEalM8Kthpe3hTzOGrF2TGbAu2uyqeA== + version "20.5.9" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.5.9.tgz#a70ec9d8fa0180a314c3ede0e20ea56ff71aed9a" + integrity sha512-PcGNd//40kHAS3sTlzKB9C9XL4K0sTup8nbG5lC14kzEteTNuAFh9u5nA0o5TWnSG2r/JNPRXFVcHJIIeRlmqQ== "@types/parse-json@^4.0.0": version "4.0.0" @@ -1863,9 +1863,9 @@ integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== "@types/qs@*": - version "6.9.7" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" - integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + version "6.9.8" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.8.tgz#f2a7de3c107b89b441e071d5472e6b726b4adf45" + integrity sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg== "@types/range-parser@*": version "1.2.4" @@ -2589,9 +2589,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001517: - version "1.0.30001522" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001522.tgz#44b87a406c901269adcdb834713e23582dd71856" - integrity sha512-TKiyTVZxJGhsTszLuzb+6vUZSjVOAhClszBr2Ta2k9IwtNBT/4dzmL6aywt0HCgEZlmwJzXJd8yNiob6HgwTRg== + version "1.0.30001525" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001525.tgz#d2e8fdec6116ffa36284ca2c33ef6d53612fe1c8" + integrity sha512-/3z+wB4icFt3r0USMwxujAqRvaD/B7rvGTsKhbhSQErVrJvkZCLhgNLJxU8MevahQVH6hCU9FsHdNUFbiwmE7Q== chalk@^2.3.2, chalk@^2.4.2: version "2.4.2" @@ -3166,18 +3166,18 @@ datatables.net-bs5@>=1.13.4, datatables.net-bs5@^1.10.20: jquery ">=1.7" datatables.net-buttons-bs5@^2.2.2: - version "2.4.1" - resolved "https://registry.yarnpkg.com/datatables.net-buttons-bs5/-/datatables.net-buttons-bs5-2.4.1.tgz#61d33c73ed00e40dc5bea94679c05879a74b7acc" - integrity sha512-z3YwAtHkCioLveY7Tu81F6NFlYZu18xjFFeXZna4o6TFP5qWPt2F+JGgD2aEpcxcJqxKWihUGHcuHf7R1SLkig== + version "2.4.2" + resolved "https://registry.yarnpkg.com/datatables.net-buttons-bs5/-/datatables.net-buttons-bs5-2.4.2.tgz#0878bfa6ec33f21325becac1dab9600324ac95a4" + integrity sha512-FIapzeW8zBZJ6/yjnbXn22gjk0ONSLykFRfxiU5w1Zjnn2e3fPSi/xN9l9NagRXFvRO80zacC4nYmYOc2cXKfg== dependencies: datatables.net-bs5 ">=1.13.4" datatables.net-buttons ">=2.3.6" jquery ">=1.7" datatables.net-buttons@>=2.3.6: - version "2.4.1" - resolved "https://registry.yarnpkg.com/datatables.net-buttons/-/datatables.net-buttons-2.4.1.tgz#a548943494a37b0b7907c7b9afcf3ebaa29c17aa" - integrity sha512-Z788xsbGW+YqJWgQYCzE5IsbY8MNIihhDP/LOefijEn7MM1/jf4sQdf/KRNHr/CWTFGfojGPz+GNdJh8appeQQ== + version "2.4.2" + resolved "https://registry.yarnpkg.com/datatables.net-buttons/-/datatables.net-buttons-2.4.2.tgz#ceb7204697a1a7932c6dd4e476fd9c9bed81a610" + integrity sha512-ps88Wk6yju8hPyqIPhHZ9xhL+pAfgoiI1nZsyPswvqk84kzcqgj1nmulSSLMYNwFG8awsU8C94gF+Pf+JhDh7g== dependencies: datatables.net ">=1.13.4" jquery ">=1.7" @@ -3388,9 +3388,9 @@ dns-equal@^1.0.0: integrity sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg== dns-packet@^5.2.2: - version "5.6.0" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.6.0.tgz#2202c947845c7a63c23ece58f2f70ff6ab4c2f7d" - integrity sha512-rza3UH1LwdHh9qyPXp8lkwpjSNk/AMD3dPytUoRoqnypDUhY0xvbdmVhWOfxO68frEfV9BU8V12Ez7ZsHGZpCQ== + version "5.6.1" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.6.1.tgz#ae888ad425a9d1478a0674256ab866de1012cf2f" + integrity sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw== dependencies: "@leichtgewicht/ip-codec" "^2.0.1" @@ -3486,9 +3486,9 @@ ee-first@1.1.1: integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== electron-to-chromium@^1.4.477: - version "1.4.499" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.499.tgz#dc36b67f4c8e273524e8d2080c5203a6a76987b6" - integrity sha512-0NmjlYBLKVHva4GABWAaHuPJolnDuL0AhV3h1hES6rcLCWEIbRL6/8TghfsVwkx6TEroQVdliX7+aLysUpKvjw== + version "1.4.508" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.508.tgz#5641ff2f5ba11df4bd960fe6a2f9f70aa8b9af96" + integrity sha512-FFa8QKjQK/A5QuFr2167myhMesGrhlOBD+3cYNxO9/S4XzHEXesyTD/1/xF644gC8buFPz3ca6G1LOQD0tZrrg== emoji-regex@^8.0.0: version "8.0.0" @@ -4486,6 +4486,11 @@ is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + is-potential-custom-element-name@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" @@ -4562,9 +4567,9 @@ jest-worker@^27.4.5: supports-color "^8.0.0" jest-worker@^29.4.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.6.3.tgz#7b1a47bbb6559f3c0882d16595938590e63915d5" - integrity sha512-wacANXecZ/GbQakpf2CClrqrlwsYYDSXFd4fIGdL+dXpM2GWoJ+6bhQ7vR3TKi3+gkSfBkjy1/khH/WrYS4Q6g== + version "29.6.4" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.6.4.tgz#f34279f4afc33c872b470d4af21b281ac616abd3" + integrity sha512-6dpvFV4WjcWbDVGgHTWo/aupl8/LbBx2NSKfiwqf79xC/yeJjKHT1+StcKy/2KTmW16hE68ccKVOtXf+WZGz7Q== dependencies: "@types/node" "*" jest-util "^29.6.3" @@ -4572,9 +4577,9 @@ jest-worker@^29.4.3: supports-color "^8.0.0" jquery@>=1.7, jquery@^3.5.1: - version "3.7.0" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.7.0.tgz#fe2c01a05da500709006d8790fe21c8a39d75612" - integrity sha512-umpJ0/k8X0MvD1ds0P9SfowREz2LenHsQaxSohMZ5OMNEU2r0tf8pdeEFTHMFxWVxKNyU9rTtK3CWzUCTKJUeQ== + version "3.7.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.7.1.tgz#083ef98927c9a6a74d05a6af02806566d16274de" + integrity sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg== js-tokens@^4.0.0: version "4.0.0" @@ -4644,7 +4649,7 @@ json-schema-traverse@^1.0.0: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== -json5@^2.1.2, json5@^2.2.2: +json5@^2.1.2, json5@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== @@ -4757,16 +4762,41 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== +lodash.escape@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98" + integrity sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw== + +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g== + +lodash.invokemap@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.invokemap/-/lodash.invokemap-4.6.0.tgz#1748cda5d8b0ef8369c4eb3ec54c21feba1f2d62" + integrity sha512-CfkycNtMqgUlfjfdh2BhKO/ZXrP8ePOX5lEU/g0R3ItJcnuxWDwokMGKx1hWcfOikmyOVx6X9IwWnDGlgKl61w== + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== +lodash.pullall@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.pullall/-/lodash.pullall-4.2.0.tgz#9d98b8518b7c965b0fae4099bd9fb7df8bbf38ba" + integrity sha512-VhqxBKH0ZxPpLhiu68YD1KnHmbhQJQctcipvmFnqIBDYzcIHzf3Zpu0tpeOKtR4x76p9yohc506eGdOjTmyIBg== + lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== +lodash.uniqby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302" + integrity sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww== + lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -4818,9 +4848,9 @@ marked@4.0.12: integrity sha512-hgibXWrEDNBWgGiK18j/4lkS6ihTe9sxtV4Q1OQppb/0zzyPSzoFANBa5MfsG/zgsWklmNnhm0XACZOH/0HBiQ== marked@^7.0.4: - version "7.0.4" - resolved "https://registry.yarnpkg.com/marked/-/marked-7.0.4.tgz#e2558ee2d535b9df6a27c6e282dc603a18388a6d" - integrity sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ== + version "7.0.5" + resolved "https://registry.yarnpkg.com/marked/-/marked-7.0.5.tgz#8a9e4e3afb93b58fe9ee7608e67cc154eb15d508" + integrity sha512-lwNAFTfXgqpt/XvK17a/8wY9/q6fcSPZT1aP6QW0u74VwaJF/Z9KbRcX23sWE4tODM+AolJNcUtErTkgOeFP/Q== mdn-data@2.0.14: version "2.0.14" @@ -5848,9 +5878,9 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^ integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== postcss@^8.2.14, postcss@^8.2.15, postcss@^8.4.12, postcss@^8.4.21, postcss@^8.4.24: - version "8.4.28" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.28.tgz#c6cc681ed00109072816e1557f889ef51cf950a5" - integrity sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw== + version "8.4.29" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.29.tgz#33bc121cf3b3688d4ddef50be869b2a54185a1dd" + integrity sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw== dependencies: nanoid "^3.3.6" picocolors "^1.0.0" @@ -6397,14 +6427,14 @@ signal-exit@^3.0.2, signal-exit@^3.0.3: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -sirv@^1.0.7: - version "1.0.19" - resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.19.tgz#1d73979b38c7fe91fcba49c85280daa9c2363b49" - integrity sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ== +sirv@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.3.tgz#ca5868b87205a74bef62a469ed0296abceccd446" + integrity sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA== dependencies: "@polka/url" "^1.0.0-next.20" mrmime "^1.0.0" - totalist "^1.0.0" + totalist "^3.0.0" slash@^3.0.0: version "3.0.0" @@ -6718,9 +6748,9 @@ terser-webpack-plugin@^5.3.0, terser-webpack-plugin@^5.3.7: terser "^5.16.8" terser@^5.16.8, terser@^5.3.4: - version "5.19.2" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.19.2.tgz#bdb8017a9a4a8de4663a7983f45c506534f9234e" - integrity sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA== + version "5.19.3" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.19.3.tgz#359baeba615aef13db4b8c4d77a2aa0d8814aa9e" + integrity sha512-pQzJ9UJzM0IgmT4FAtYI6+VqFf0lj/to58AV0Xfgg0Up37RyPG7Al+1cepC6/BVuAxR9oNb41/DL4DEoHJvTdg== dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.8.2" @@ -6795,10 +6825,10 @@ tom-select@^2.1.0: "@orchidjs/sifter" "^1.0.3" "@orchidjs/unicode-variants" "^1.0.4" -totalist@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" - integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g== +totalist@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8" + integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ== tough-cookie@^4.0.0: version "4.1.3" @@ -7040,19 +7070,26 @@ webidl-conversions@^6.1.0: integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== webpack-bundle-analyzer@^4.3.0: - version "4.9.0" - resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.9.0.tgz#fc093c4ab174fd3dcbd1c30b763f56d10141209d" - integrity sha512-+bXGmO1LyiNx0i9enBu3H8mv42sj/BJWhZNFwjz92tVnBa9J3JMGo2an2IXlEleoDOPn/Hofl5hr/xCpObUDtw== + version "4.9.1" + resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.9.1.tgz#d00bbf3f17500c10985084f22f1a2bf45cb2f09d" + integrity sha512-jnd6EoYrf9yMxCyYDPj8eutJvtjQNp8PHmni/e/ulydHBWhT5J3menXt3HEkScsu9YqMAcG4CfFjs3rj5pVU1w== dependencies: "@discoveryjs/json-ext" "0.5.7" acorn "^8.0.4" acorn-walk "^8.0.0" - chalk "^4.1.0" commander "^7.2.0" + escape-string-regexp "^4.0.0" gzip-size "^6.0.0" - lodash "^4.17.20" + is-plain-object "^5.0.0" + lodash.debounce "^4.0.8" + lodash.escape "^4.0.1" + lodash.flatten "^4.4.0" + lodash.invokemap "^4.6.0" + lodash.pullall "^4.2.0" + lodash.uniqby "^4.7.0" opener "^1.5.2" - sirv "^1.0.7" + picocolors "^1.0.0" + sirv "^2.0.3" ws "^7.3.1" webpack-cli@^4.10.0: From e04b635c98924e4214aee089a0fe284cdd638b37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 3 Sep 2023 17:15:18 +0200 Subject: [PATCH 22/62] Added API endpoints for more data structures --- src/Entity/Base/AbstractCompany.php | 3 + .../Base/AbstractStructuralDBElement.php | 4 +- src/Entity/Parts/Category.php | 60 +++++++++++++++---- src/Entity/Parts/Footprint.php | 43 +++++++++++++ src/Entity/Parts/MeasurementUnit.php | 47 ++++++++++++++- src/Entity/PriceInformations/Currency.php | 47 ++++++++++++++- src/Serializer/BigNumberNormalizer.php | 20 ++++++- tests/Serializer/BigNumberNormalizerTest.php | 27 +++++++++ 8 files changed, 233 insertions(+), 18 deletions(-) diff --git a/src/Entity/Base/AbstractCompany.php b/src/Entity/Base/AbstractCompany.php index b552d705..e1f5e757 100644 --- a/src/Entity/Base/AbstractCompany.php +++ b/src/Entity/Base/AbstractCompany.php @@ -82,6 +82,9 @@ abstract class AbstractCompany extends AbstractPartsContainingDBElement #[ORM\Column(type: Types::STRING)] protected string $website = ''; + #[Groups(['company:read', 'company:write'])] + protected string $comment = ''; + /** * @var string */ diff --git a/src/Entity/Base/AbstractStructuralDBElement.php b/src/Entity/Base/AbstractStructuralDBElement.php index 59d374c8..15550b50 100644 --- a/src/Entity/Base/AbstractStructuralDBElement.php +++ b/src/Entity/Base/AbstractStructuralDBElement.php @@ -75,7 +75,7 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement final public const PATH_DELIMITER_ARROW = ' → '; /** - * @var string The comment info for this element + * @var string The comment info for this element as markdown */ #[Groups(['full', 'import'])] #[ORM\Column(type: Types::TEXT)] @@ -221,7 +221,7 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement } /** - * Get the comment of the element. + * Get the comment of the element as markdown encoded string. * * @return string the comment diff --git a/src/Entity/Parts/Category.php b/src/Entity/Parts/Category.php index ac810cf8..c1d2e917 100644 --- a/src/Entity/Parts/Category.php +++ b/src/Entity/Parts/Category.php @@ -22,8 +22,17 @@ declare(strict_types=1); namespace App\Entity\Parts; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Serializer\Filter\PropertyFilter; use App\Entity\Attachments\Attachment; -use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\Parts\CategoryRepository; use Doctrine\DBAL\Types\Types; use Doctrine\Common\Collections\ArrayCollection; @@ -45,6 +54,29 @@ use Symfony\Component\Validator\Constraints as Assert; #[ORM\Table(name: '`categories`')] #[ORM\Index(name: 'category_idx_name', columns: ['name'])] #[ORM\Index(name: 'category_idx_parent_name', columns: ['parent_id', 'name'])] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@categories.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['category:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['category:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiResource( + uriTemplate: '/categories/{id}/children.{_format}', + operations: [ + new GetCollection(openapiContext: ['summary' => 'Retrieves the children elements of a category.'], + security: 'is_granted("@categories.read")') + ], + uriVariables: [ + 'id' => new Link(fromProperty: 'children', fromClass: Category::class) + ], + normalizationContext: ['groups' => ['category:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] +)] +#[ApiFilter(PropertyFilter::class)] class Category extends AbstractPartsContainingDBElement { #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')] @@ -53,61 +85,66 @@ class Category extends AbstractPartsContainingDBElement #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] #[ORM\JoinColumn(name: 'parent_id')] + #[Groups(['category:read', 'category:write'])] + #[ApiProperty(readableLink: false, writableLink: false)] protected ?AbstractStructuralDBElement $parent = null; + #[Groups(['category:read', 'category:write'])] + protected string $comment = ''; + /** * @var string */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'category:read', 'category:write'])] #[ORM\Column(type: Types::TEXT)] protected string $partname_hint = ''; /** * @var string */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'category:read', 'category:write'])] #[ORM\Column(type: Types::TEXT)] protected string $partname_regex = ''; /** * @var bool */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'category:read', 'category:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $disable_footprints = false; /** * @var bool */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'category:read', 'category:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $disable_manufacturers = false; /** * @var bool */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'category:read', 'category:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $disable_autodatasheets = false; /** * @var bool */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'category:read', 'category:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $disable_properties = false; /** * @var string */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'category:read', 'category:write'])] #[ORM\Column(type: Types::TEXT)] protected string $default_description = ''; /** * @var string */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'category:read', 'category:write'])] #[ORM\Column(type: Types::TEXT)] protected string $default_comment = ''; @@ -115,19 +152,20 @@ class Category extends AbstractPartsContainingDBElement * @var Collection */ #[Assert\Valid] - #[Groups(['full'])] + #[Groups(['full', 'category:read'])] #[ORM\OneToMany(targetEntity: CategoryAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['name' => 'ASC'])] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: CategoryAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] + #[Groups(['category:read'])] protected ?Attachment $master_picture_attachment = null; /** @var Collection */ #[Assert\Valid] - #[Groups(['full'])] + #[Groups(['full', 'category:read'])] #[ORM\OneToMany(targetEntity: CategoryParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] protected Collection $parameters; diff --git a/src/Entity/Parts/Footprint.php b/src/Entity/Parts/Footprint.php index 4126a63b..c7148deb 100644 --- a/src/Entity/Parts/Footprint.php +++ b/src/Entity/Parts/Footprint.php @@ -22,6 +22,16 @@ declare(strict_types=1); namespace App\Entity\Parts; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Serializer\Filter\PropertyFilter; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\Parts\FootprintRepository; @@ -32,6 +42,7 @@ use App\Entity\Base\AbstractPartsContainingDBElement; use App\Entity\Parameters\FootprintParameter; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; /** @@ -43,26 +54,56 @@ use Symfony\Component\Validator\Constraints as Assert; #[ORM\Table('`footprints`')] #[ORM\Index(name: 'footprint_idx_name', columns: ['name'])] #[ORM\Index(name: 'footprint_idx_parent_name', columns: ['parent_id', 'name'])] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@footprints.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['footprint:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['footprint:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiResource( + uriTemplate: '/footprints/{id}/children.{_format}', + operations: [ + new GetCollection(openapiContext: ['summary' => 'Retrieves the children elements of a footprint.'], + security: 'is_granted("@footprints.read")') + ], + uriVariables: [ + 'id' => new Link(fromProperty: 'children', fromClass: Footprint::class) + ], + normalizationContext: ['groups' => ['footprint:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] +)] +#[ApiFilter(PropertyFilter::class)] class Footprint extends AbstractPartsContainingDBElement { #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] #[ORM\JoinColumn(name: 'parent_id')] + #[Groups(['footprint:read', 'footprint:write'])] + #[ApiProperty(readableLink: false, writableLink: false)] protected ?AbstractStructuralDBElement $parent = null; #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')] #[ORM\OrderBy(['name' => 'ASC'])] protected Collection $children; + #[Groups(['footprint:read', 'footprint:write'])] + protected string $comment = ''; + /** * @var Collection */ #[Assert\Valid] #[ORM\OneToMany(targetEntity: FootprintAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['name' => 'ASC'])] + #[Groups(['footprint:read'])] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: FootprintAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] + #[Groups(['footprint:read'])] protected ?Attachment $master_picture_attachment = null; /** @@ -70,6 +111,7 @@ class Footprint extends AbstractPartsContainingDBElement */ #[ORM\ManyToOne(targetEntity: FootprintAttachment::class)] #[ORM\JoinColumn(name: 'id_footprint_3d')] + #[Groups(['footprint:read'])] protected ?FootprintAttachment $footprint_3d = null; /** @var Collection @@ -77,6 +119,7 @@ class Footprint extends AbstractPartsContainingDBElement #[Assert\Valid] #[ORM\OneToMany(targetEntity: FootprintParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[Groups(['footprint:read'])] protected Collection $parameters; /**************************************** diff --git a/src/Entity/Parts/MeasurementUnit.php b/src/Entity/Parts/MeasurementUnit.php index 538d4c57..d54ecf3b 100644 --- a/src/Entity/Parts/MeasurementUnit.php +++ b/src/Entity/Parts/MeasurementUnit.php @@ -22,6 +22,16 @@ declare(strict_types=1); namespace App\Entity\Parts; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Serializer\Filter\PropertyFilter; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\Parts\MeasurementUnitRepository; @@ -48,6 +58,29 @@ use Symfony\Component\Validator\Constraints as Assert; #[ORM\Table(name: '`measurement_units`')] #[ORM\Index(name: 'unit_idx_name', columns: ['name'])] #[ORM\Index(name: 'unit_idx_parent_name', columns: ['parent_id', 'name'])] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@measurement_unit.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['measurement_unit:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['measurement_unit:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiResource( + uriTemplate: '/footprints/{id}/children.{_format}', + operations: [ + new GetCollection(openapiContext: ['summary' => 'Retrieves the children elements of a MeasurementUnit.'], + security: 'is_granted("@measurement_unit.read")') + ], + uriVariables: [ + 'id' => new Link(fromProperty: 'children', fromClass: MeasurementUnit::class) + ], + normalizationContext: ['groups' => ['measurement_unit:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] +)] +#[ApiFilter(PropertyFilter::class)] class MeasurementUnit extends AbstractPartsContainingDBElement { /** @@ -55,15 +88,18 @@ class MeasurementUnit extends AbstractPartsContainingDBElement * or m (for meters). */ #[Assert\Length(max: 10)] - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'measurement_unit:read', 'measurement_unit:write'])] #[ORM\Column(type: Types::STRING, name: 'unit', nullable: true)] protected ?string $unit = null; + #[Groups(['measurement_unit:read', 'measurement_unit:write'])] + protected string $comment = ''; + /** * @var bool Determines if the amount value associated with this unit should be treated as integer. * Set to false, to measure continuous sizes likes masses or lengths. */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'measurement_unit:read', 'measurement_unit:write'])] #[ORM\Column(type: Types::BOOLEAN, name: 'is_integer')] protected bool $is_integer = false; @@ -72,7 +108,7 @@ class MeasurementUnit extends AbstractPartsContainingDBElement * Useful for sizes like meters. For this the unit must be set */ #[Assert\Expression('this.isUseSIPrefix() == false or this.getUnit() != null', message: 'validator.measurement_unit.use_si_prefix_needs_unit')] - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'measurement_unit:read', 'measurement_unit:write'])] #[ORM\Column(type: Types::BOOLEAN, name: 'use_si_prefix')] protected bool $use_si_prefix = false; @@ -82,6 +118,8 @@ class MeasurementUnit extends AbstractPartsContainingDBElement #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] #[ORM\JoinColumn(name: 'parent_id')] + #[Groups('measurement_unit:read', 'measurement_unit:write')] + #[ApiProperty(readableLink: false, writableLink: false)] protected ?AbstractStructuralDBElement $parent = null; /** @@ -90,10 +128,12 @@ class MeasurementUnit extends AbstractPartsContainingDBElement #[Assert\Valid] #[ORM\OneToMany(targetEntity: MeasurementUnitAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['name' => 'ASC'])] + #[Groups(['measurement_unit:read'])] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: MeasurementUnitAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] + #[Groups(['measurement_unit:read'])] protected ?Attachment $master_picture_attachment = null; /** @var Collection @@ -101,6 +141,7 @@ class MeasurementUnit extends AbstractPartsContainingDBElement #[Assert\Valid] #[ORM\OneToMany(targetEntity: MeasurementUnitParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[Groups(['measurement_unit:read'])] protected Collection $parameters; /** diff --git a/src/Entity/PriceInformations/Currency.php b/src/Entity/PriceInformations/Currency.php index 1aec8d8a..ddc17610 100644 --- a/src/Entity/PriceInformations/Currency.php +++ b/src/Entity/PriceInformations/Currency.php @@ -22,8 +22,19 @@ declare(strict_types=1); namespace App\Entity\PriceInformations; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Serializer\Filter\PropertyFilter; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentTypeAttachment; +use App\Entity\Parts\Footprint; use App\Repository\CurrencyRepository; use Doctrine\DBAL\Types\Types; use App\Entity\Attachments\CurrencyAttachment; @@ -49,6 +60,29 @@ use Symfony\Component\Validator\Constraints as Assert; #[ORM\Table(name: 'currencies')] #[ORM\Index(name: 'currency_idx_name', columns: ['name'])] #[ORM\Index(name: 'currency_idx_parent_name', columns: ['parent_id', 'name'])] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@currencies.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['currency:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['currency:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiResource( + uriTemplate: '/currencies/{id}/children.{_format}', + operations: [ + new GetCollection(openapiContext: ['summary' => 'Retrieves the children elements of a currency.'], + security: 'is_granted("@currencies.read")') + ], + uriVariables: [ + 'id' => new Link(fromProperty: 'children', fromClass: Currency::class) + ], + normalizationContext: ['groups' => ['currency:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] +)] +#[ApiFilter(PropertyFilter::class)] class Currency extends AbstractStructuralDBElement { final public const PRICE_SCALE = 5; @@ -59,14 +93,19 @@ class Currency extends AbstractStructuralDBElement */ #[ORM\Column(type: 'big_decimal', precision: 11, scale: 5, nullable: true)] #[BigDecimalPositive()] + #[Groups(['currency:read', 'currency:write'])] + #[ApiProperty(readableLink: false, writableLink: false)] protected ?BigDecimal $exchange_rate = null; + #[Groups(['currency:read', 'currency:write'])] + protected string $comment = ""; + /** * @var string the 3-letter ISO code of the currency */ #[Assert\Currency] #[Assert\NotBlank] - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'currency:read', 'currency:write'])] #[ORM\Column(type: Types::STRING)] protected string $iso_code = ""; @@ -76,6 +115,8 @@ class Currency extends AbstractStructuralDBElement #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] #[ORM\JoinColumn(name: 'parent_id')] + #[Groups(['currency:read', 'currency:write'])] + #[ApiProperty(readableLink: false, writableLink: false)] protected ?AbstractStructuralDBElement $parent = null; /** @@ -84,10 +125,12 @@ class Currency extends AbstractStructuralDBElement #[Assert\Valid] #[ORM\OneToMany(targetEntity: CurrencyAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['name' => 'ASC'])] + #[Groups(['currency:read', 'currency:write'])] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: CurrencyAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] + #[Groups(['currency:read', 'currency:write'])] protected ?Attachment $master_picture_attachment = null; /** @var Collection @@ -95,6 +138,7 @@ class Currency extends AbstractStructuralDBElement #[Assert\Valid] #[ORM\OneToMany(targetEntity: CurrencyParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[Groups(['currency:read', 'currency:write'])] protected Collection $parameters; /** @var Collection @@ -136,6 +180,7 @@ class Currency extends AbstractStructuralDBElement /** * Returns the inverse exchange rate (how many of the current currency the base unit is worth). */ + #[Groups(['currency:read'])] public function getInverseExchangeRate(): ?BigDecimal { $tmp = $this->getExchangeRate(); diff --git a/src/Serializer/BigNumberNormalizer.php b/src/Serializer/BigNumberNormalizer.php index 8bb686ee..80bb98d7 100644 --- a/src/Serializer/BigNumberNormalizer.php +++ b/src/Serializer/BigNumberNormalizer.php @@ -22,13 +22,15 @@ declare(strict_types=1); */ namespace App\Serializer; +use Brick\Math\BigDecimal; use Brick\Math\BigNumber; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; /** * @see \App\Tests\Serializer\BigNumberNormalizerTest */ -class BigNumberNormalizer implements NormalizerInterface +class BigNumberNormalizer implements NormalizerInterface, DenormalizerInterface { public function supportsNormalization($data, string $format = null, array $context = []): bool @@ -52,6 +54,22 @@ class BigNumberNormalizer implements NormalizerInterface { return [ BigNumber::class => true, + BigDecimal::class => true, ]; } + + public function denormalize(mixed $data, string $type, string $format = null, array $context = []) + { + if (!is_a($type, BigNumber::class, true)) { + throw new \InvalidArgumentException('This normalizer only supports BigNumber objects!'); + } + + return $type::of($data); + } + + public function supportsDenormalization(mixed $data, string $type, string $format = null) + { + //data must be a string or a number (int, float, etc.) and the type must be BigNumber or BigDecimal + return (is_string($data) || is_numeric($data)) && (is_subclass_of($type, BigNumber::class)); + } } diff --git a/tests/Serializer/BigNumberNormalizerTest.php b/tests/Serializer/BigNumberNormalizerTest.php index 6e3ed79b..f64347ee 100644 --- a/tests/Serializer/BigNumberNormalizerTest.php +++ b/tests/Serializer/BigNumberNormalizerTest.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace App\Tests\Serializer; use App\Serializer\BigNumberNormalizer; +use Brick\Math\BigInteger; use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Brick\Math\BigDecimal; @@ -57,4 +58,30 @@ class BigNumberNormalizerTest extends WebTestCase $bigDecimal = BigDecimal::of(1); $this->assertTrue($this->service->supportsNormalization($bigDecimal)); } + + public function testSupportsDenormalization(): void + { + //Denormalizer must only support BigNumber objects (and child classes) + $this->assertFalse($this->service->supportsDenormalization("1.23", \stdClass::class)); + + //Denormalizer must only support number like input data + $this->assertFalse($this->service->supportsDenormalization(new \stdClass(), BigDecimal::class)); + + //Using the right class and data type + $this->assertTrue($this->service->supportsDenormalization("1.23", BigDecimal::class)); + $this->assertTrue($this->service->supportsDenormalization("123", BigInteger::class)); + $this->assertTrue($this->service->supportsDenormalization(123, BigInteger::class)); + $this->assertTrue($this->service->supportsDenormalization(12.3, BigDecimal::class)); + } + + public function testDenormalize(): void + { + $bigDecimal = $this->service->denormalize("1.23456789", BigDecimal::class); + $this->assertInstanceOf(BigDecimal::class, $bigDecimal); + $this->assertSame('1.23456789', (string) $bigDecimal); + + $bigInteger = $this->service->denormalize(1234, BigInteger::class); + $this->assertInstanceOf(BigInteger::class, $bigInteger); + $this->assertSame('1234', (string) $bigInteger); + } } From 09acca950d6249af4c528e7745e9ab80624c409b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 3 Sep 2023 23:58:09 +0200 Subject: [PATCH 23/62] Added simple endpoint for basic part infos and partlots --- src/Entity/Parts/InfoProviderReference.php | 5 +++ src/Entity/Parts/MeasurementUnit.php | 4 +-- src/Entity/Parts/Part.php | 21 +++++++++++ src/Entity/Parts/PartLot.php | 36 +++++++++++++++---- .../PartTraits/AdvancedPropertyTrait.php | 12 ++++--- .../Parts/PartTraits/BasicPropertyTrait.php | 10 +++--- src/Entity/Parts/PartTraits/InstockTrait.php | 4 +-- .../Parts/PartTraits/ManufacturerTrait.php | 8 ++--- src/Entity/Parts/PartTraits/ProjectTrait.php | 5 ++- 9 files changed, 77 insertions(+), 28 deletions(-) diff --git a/src/Entity/Parts/InfoProviderReference.php b/src/Entity/Parts/InfoProviderReference.php index 53b81a0a..ea0fae7f 100644 --- a/src/Entity/Parts/InfoProviderReference.php +++ b/src/Entity/Parts/InfoProviderReference.php @@ -27,6 +27,7 @@ use App\Services\InfoProviderSystem\DTOs\SearchResultDTO; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Embeddable; +use Symfony\Component\Serializer\Annotation\Groups; /** * This class represents a reference to a info provider inside a part. @@ -37,19 +38,23 @@ class InfoProviderReference /** @var string|null The key referencing the provider used to get this part, or null if it was not provided by a data provider */ #[Column(type: 'string', nullable: true)] + #[Groups(['provider_reference:read'])] private ?string $provider_key = null; /** @var string|null The id of this part inside the provider system or null if the part was not provided by a data provider */ #[Column(type: 'string', nullable: true)] + #[Groups(['provider_reference:read'])] private ?string $provider_id = null; /** * @var string|null The url of this part inside the provider system or null if this info is not existing */ #[Column(type: 'string', nullable: true)] + #[Groups(['provider_reference:read'])] private ?string $provider_url = null; #[Column(type: Types::DATETIME_MUTABLE, nullable: true, options: ['default' => null])] + #[Groups(['provider_reference:read'])] private ?\DateTimeInterface $last_updated = null; /** diff --git a/src/Entity/Parts/MeasurementUnit.php b/src/Entity/Parts/MeasurementUnit.php index d54ecf3b..18c6e2ed 100644 --- a/src/Entity/Parts/MeasurementUnit.php +++ b/src/Entity/Parts/MeasurementUnit.php @@ -61,7 +61,7 @@ use Symfony\Component\Validator\Constraints as Assert; #[ApiResource( operations: [ new Get(security: 'is_granted("read", object)'), - new GetCollection(security: 'is_granted("@measurement_unit.read")'), + new GetCollection(security: 'is_granted("@measurement_units.read")'), new Post(securityPostDenormalize: 'is_granted("create", object)'), new Patch(security: 'is_granted("edit", object)'), new Delete(security: 'is_granted("delete", object)'), @@ -73,7 +73,7 @@ use Symfony\Component\Validator\Constraints as Assert; uriTemplate: '/footprints/{id}/children.{_format}', operations: [ new GetCollection(openapiContext: ['summary' => 'Retrieves the children elements of a MeasurementUnit.'], - security: 'is_granted("@measurement_unit.read")') + security: 'is_granted("@measurement_units.read")') ], uriVariables: [ 'id' => new Link(fromProperty: 'children', fromClass: MeasurementUnit::class) diff --git a/src/Entity/Parts/Part.php b/src/Entity/Parts/Part.php index 5916569b..096c36b0 100644 --- a/src/Entity/Parts/Part.php +++ b/src/Entity/Parts/Part.php @@ -22,6 +22,15 @@ declare(strict_types=1); namespace App\Entity\Parts; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Serializer\Filter\PropertyFilter; use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\PartRepository; use Doctrine\DBAL\Types\Types; @@ -60,6 +69,18 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; #[ORM\Index(name: 'parts_idx_datet_name_last_id_needs', columns: ['datetime_added', 'name', 'last_modified', 'id', 'needs_review'])] #[ORM\Index(name: 'parts_idx_name', columns: ['name'])] #[ORM\Index(name: 'parts_idx_ipn', columns: ['ipn'])] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@parts.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:read', 'provider_reference:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['part:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiFilter(PropertyFilter::class)] class Part extends AttachmentContainingDBElement { use AdvancedPropertyTrait; diff --git a/src/Entity/Parts/PartLot.php b/src/Entity/Parts/PartLot.php index e552c06a..b4f22eab 100644 --- a/src/Entity/Parts/PartLot.php +++ b/src/Entity/Parts/PartLot.php @@ -22,6 +22,14 @@ declare(strict_types=1); namespace App\Entity\Parts; +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\Repository\PartLotRepository; use Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractDBElement; @@ -50,6 +58,18 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; #[ORM\Index(name: 'part_lots_idx_instock_un_expiration_id_part', columns: ['instock_unknown', 'expiration_date', 'id_part'])] #[ORM\Index(name: 'part_lots_idx_needs_refill', columns: ['needs_refill'])] #[ValidPartLot] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@parts.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_lot:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['part_lot:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiFilter(PropertyFilter::class)] class PartLot extends AbstractDBElement implements TimeStampableInterface, NamedElementInterface { use TimestampTrait; @@ -57,14 +77,14 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named /** * @var string A short description about this lot, shown in table */ - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'part_lot:read', 'part_lot:write'])] #[ORM\Column(type: Types::TEXT)] protected string $description = ''; /** * @var string a comment stored with this lot */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'part_lot:read', 'part_lot:write'])] #[ORM\Column(type: Types::TEXT)] protected string $comment = ''; @@ -72,14 +92,14 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named * @var \DateTimeInterface|null Set a time until when the lot must be used. * Set to null, if the lot can be used indefinitely. */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part_lot:read', 'part_lot:write'])] #[ORM\Column(type: Types::DATETIME_MUTABLE, name: 'expiration_date', nullable: true)] protected ?\DateTimeInterface $expiration_date = null; /** * @var Storelocation|null The storelocation of this lot */ - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'part_lot:read', 'part_lot:write'])] #[ORM\ManyToOne(targetEntity: Storelocation::class, fetch: 'EAGER')] #[ORM\JoinColumn(name: 'id_store_location')] #[Selectable()] @@ -88,7 +108,7 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named /** * @var bool If this is set to true, the instock amount is marked as not known */ - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'part_lot:read', 'part_lot:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $instock_unknown = false; @@ -96,14 +116,14 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named * @var float For continuous sizes (length, volume, etc.) the instock is saved here. */ #[Assert\PositiveOrZero] - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'part_lot:read', 'part_lot:write'])] #[ORM\Column(type: Types::FLOAT)] protected float $amount = 0.0; /** * @var bool determines if this lot was manually marked for refilling */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part_lot:read', 'part_lot:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $needs_refill = false; @@ -113,6 +133,7 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named #[Assert\NotNull] #[ORM\ManyToOne(targetEntity: Part::class, inversedBy: 'partLots')] #[ORM\JoinColumn(name: 'id_part', nullable: false, onDelete: 'CASCADE')] + #[Groups(['part_lot:read', 'part_lot:write'])] protected ?Part $part = null; /** @@ -120,6 +141,7 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named */ #[ORM\ManyToOne(targetEntity: User::class)] #[ORM\JoinColumn(name: 'id_owner', onDelete: 'SET NULL')] + #[Groups(['part_lot:read', 'part_lot:write'])] protected ?User $owner = null; public function __clone() diff --git a/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php b/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php index 648cf2a5..712d1c2a 100644 --- a/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php +++ b/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Parts\PartTraits; +use ApiPlatform\Metadata\ApiProperty; use App\Entity\Parts\InfoProviderReference; use Doctrine\DBAL\Types\Types; use App\Entity\Parts\Part; @@ -37,14 +38,14 @@ trait AdvancedPropertyTrait /** * @var bool Determines if this part entry needs review (for example, because it is work in progress) */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $needs_review = false; /** * @var string a comma separated list of tags, associated with the part */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::TEXT)] protected string $tags = ''; @@ -52,7 +53,7 @@ trait AdvancedPropertyTrait * @var float|null how much a single part unit weighs in grams */ #[Assert\PositiveOrZero] - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::FLOAT, nullable: true)] protected ?float $mass = null; @@ -60,14 +61,15 @@ trait AdvancedPropertyTrait * @var string|null The internal part number of the part */ #[Assert\Length(max: 100)] - #[Groups(['extended', 'full', 'import'])] - #[ORM\Column(type: Types::STRING, length: 100, nullable: true, unique: true)] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] + #[ORM\Column(type: Types::STRING, length: 100, unique: true, nullable: true)] protected ?string $ipn = null; /** * @var InfoProviderReference The reference to the info provider, that provided the information about this part */ #[ORM\Embedded(class: InfoProviderReference::class, columnPrefix: 'provider_reference_')] + #[Groups(['full', 'part:read'])] protected InfoProviderReference $providerReference; /** diff --git a/src/Entity/Parts/PartTraits/BasicPropertyTrait.php b/src/Entity/Parts/PartTraits/BasicPropertyTrait.php index b0f593d6..ea68db38 100644 --- a/src/Entity/Parts/PartTraits/BasicPropertyTrait.php +++ b/src/Entity/Parts/PartTraits/BasicPropertyTrait.php @@ -35,14 +35,14 @@ trait BasicPropertyTrait /** * @var string A text describing what this part does */ - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::TEXT)] protected string $description = ''; /** * @var string A comment/note related to this part */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::TEXT)] protected string $comment = ''; @@ -55,7 +55,7 @@ trait BasicPropertyTrait /** * @var bool true, if the part is marked as favorite */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $favorite = false; @@ -65,7 +65,7 @@ trait BasicPropertyTrait */ #[Assert\NotNull(message: 'validator.select_valid_category')] #[Selectable()] - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', "part:read", "part:write"])] #[ORM\ManyToOne(targetEntity: Category::class)] #[ORM\JoinColumn(name: 'id_category', nullable: false)] protected ?Category $category = null; @@ -73,7 +73,7 @@ trait BasicPropertyTrait /** * @var Footprint|null The footprint of this part (e.g. DIP8) */ - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\ManyToOne(targetEntity: Footprint::class)] #[ORM\JoinColumn(name: 'id_footprint')] #[Selectable()] diff --git a/src/Entity/Parts/PartTraits/InstockTrait.php b/src/Entity/Parts/PartTraits/InstockTrait.php index 068173a7..f8d27cb3 100644 --- a/src/Entity/Parts/PartTraits/InstockTrait.php +++ b/src/Entity/Parts/PartTraits/InstockTrait.php @@ -49,14 +49,14 @@ trait InstockTrait * Given in the partUnit. */ #[Assert\PositiveOrZero] - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::FLOAT)] protected float $minamount = 0; /** * @var ?MeasurementUnit the unit in which the part's amount is measured */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\ManyToOne(targetEntity: MeasurementUnit::class)] #[ORM\JoinColumn(name: 'id_part_unit')] protected ?MeasurementUnit $partUnit = null; diff --git a/src/Entity/Parts/PartTraits/ManufacturerTrait.php b/src/Entity/Parts/PartTraits/ManufacturerTrait.php index 97ef246b..12643768 100644 --- a/src/Entity/Parts/PartTraits/ManufacturerTrait.php +++ b/src/Entity/Parts/PartTraits/ManufacturerTrait.php @@ -39,7 +39,7 @@ trait ManufacturerTrait /** * @var Manufacturer|null The manufacturer of this part */ - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\ManyToOne(targetEntity: Manufacturer::class)] #[ORM\JoinColumn(name: 'id_manufacturer')] #[Selectable()] @@ -49,21 +49,21 @@ trait ManufacturerTrait * @var string the url to the part on the manufacturer's homepage */ #[Assert\Url] - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::TEXT)] protected string $manufacturer_product_url = ''; /** * @var string The product number used by the manufacturer. If this is set to "", the name field is used. */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::STRING)] protected string $manufacturer_product_number = ''; /** * @var ManufacturingStatus|null The production status of this part. Can be one of the specified ones. */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::STRING, length: 255, nullable: true, enumType: ManufacturingStatus::class)] protected ?ManufacturingStatus $manufacturing_status = ManufacturingStatus::NOT_SET; diff --git a/src/Entity/Parts/PartTraits/ProjectTrait.php b/src/Entity/Parts/PartTraits/ProjectTrait.php index 51208b6a..71673f06 100644 --- a/src/Entity/Parts/PartTraits/ProjectTrait.php +++ b/src/Entity/Parts/PartTraits/ProjectTrait.php @@ -9,12 +9,10 @@ use App\Entity\ProjectSystem\ProjectBOMEntry; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Groups; trait ProjectTrait { - /** - * @var Collection $project_bom_entries - */ /** * @var Collection $project_bom_entries */ @@ -42,6 +40,7 @@ trait ProjectTrait * Checks whether this part represents the builds of a project * @return bool True if it represents the builds, false if not */ + #[Groups(['part:read'])] public function isProjectBuildPart(): bool { return $this->built_project !== null; From 0af5a58dbe071575326d7f55b16fffd97bce90ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 4 Sep 2023 22:57:40 +0200 Subject: [PATCH 24/62] Renamed Storelocation entity to StorageLocation --- .../Migrations/ConvertBBCodeCommand.php | 4 +- ...ller.php => StorageLocationController.php} | 22 ++++---- src/Controller/PartController.php | 6 +-- src/Controller/PartListsController.php | 6 +-- src/Controller/TreeController.php | 6 +-- src/Controller/TypeaheadController.php | 4 +- src/DataFixtures/DataStructureFixtures.php | 4 +- src/DataFixtures/PartFixtures.php | 6 +-- src/DataTables/Filters/PartFilter.php | 4 +- src/DataTables/PartsDataTable.php | 4 +- src/Entity/Attachments/Attachment.php | 8 ++- ...ment.php => StorageLocationAttachment.php} | 12 ++--- src/Entity/Base/AbstractDBElement.php | 6 +-- .../LabelSystem/LabelSupportedElement.php | 4 +- src/Entity/LogSystem/AbstractLogEntry.php | 2 +- .../LogSystem/CollectionElementDeleted.php | 14 ++--- src/Entity/LogSystem/LogTargetType.php | 4 +- src/Entity/Parameters/AbstractParameter.php | 6 ++- ...meter.php => StorageLocationParameter.php} | 10 ++-- src/Entity/Parts/PartLot.php | 12 ++--- ...{Storelocation.php => StorageLocation.php} | 54 +++++++++++++++---- src/Form/Filters/AttachmentFilterType.php | 4 +- src/Form/Filters/LogFilterType.php | 2 +- src/Form/Filters/PartFilterType.php | 4 +- src/Form/ParameterType.php | 4 +- src/Form/Part/PartLotType.php | 4 +- src/Form/Type/PartLotSelectType.php | 4 +- .../Parts/StorelocationRepository.php | 6 +-- src/Security/Voter/AttachmentVoter.php | 4 +- src/Security/Voter/ParameterVoter.php | 4 +- src/Security/Voter/StructureVoter.php | 4 +- src/Serializer/PartNormalizer.php | 4 +- .../Attachments/AttachmentSubmitHandler.php | 4 +- .../Attachments/PartPreviewGenerator.php | 4 +- src/Services/ElementTypeNameGenerator.php | 4 +- src/Services/EntityURLGenerator.php | 16 +++--- .../PKDatastructureImporter.php | 8 +-- .../PartKeeprImporter/PKPartImporter.php | 4 +- .../Barcodes/BarcodeContentGenerator.php | 6 +-- .../LabelExampleElementsGenerator.php | 10 ++-- src/Services/LabelSystem/LabelGenerator.php | 2 +- .../PlaceholderProviders/PartLotProvider.php | 6 +-- .../StorelocationProvider.php | 4 +- .../LabelSystem/SandboxedTwigProvider.php | 4 +- .../Parts/PartLotWithdrawAddHelper.php | 4 +- src/Services/Tools/StatisticsHelper.php | 4 +- src/Services/Trees/ToolsTreeBuilder.php | 4 +- src/Services/Trees/TreeViewGenerator.php | 6 +-- src/Twig/EntityExtension.php | 4 +- .../Constraints/ValidPartLotValidator.php | 6 +-- .../StorelocationControllerTest.php | 4 +- tests/Entity/Attachments/AttachmentTest.php | 6 +-- .../Entity/LogSystem/AbstractLogEntryTest.php | 2 +- .../Barcodes/BarcodeContentGeneratorTest.php | 6 +-- .../LabelSystem/LabelGeneratorTest.php | 4 +- .../PartLotProviderTest.php | 6 +-- .../LabelSystem/SandboxedTwigProviderTest.php | 6 +-- .../Parts/PartLotWithdrawAddHelperTest.php | 14 ++--- tests/Twig/EntityExtensionTest.php | 4 +- 59 files changed, 218 insertions(+), 176 deletions(-) rename src/Controller/AdminPages/{StorelocationController.php => StorageLocationController.php} (75%) rename src/Entity/Attachments/{StorelocationAttachment.php => StorageLocationAttachment.php} (77%) rename src/Entity/Parameters/{StorelocationParameter.php => StorageLocationParameter.php} (87%) rename src/Entity/Parts/{Storelocation.php => StorageLocation.php} (76%) diff --git a/src/Command/Migrations/ConvertBBCodeCommand.php b/src/Command/Migrations/ConvertBBCodeCommand.php index 2297cbdd..2b6e4382 100644 --- a/src/Command/Migrations/ConvertBBCodeCommand.php +++ b/src/Command/Migrations/ConvertBBCodeCommand.php @@ -30,7 +30,7 @@ use App\Entity\Parts\Category; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; use App\Entity\UserSystem\Group; @@ -85,7 +85,7 @@ class ConvertBBCodeCommand extends Command return [ Part::class => ['description', 'comment'], AttachmentType::class => ['comment'], - Storelocation::class => ['comment'], + StorageLocation::class => ['comment'], Project::class => ['comment'], Category::class => ['comment'], Manufacturer::class => ['comment'], diff --git a/src/Controller/AdminPages/StorelocationController.php b/src/Controller/AdminPages/StorageLocationController.php similarity index 75% rename from src/Controller/AdminPages/StorelocationController.php rename to src/Controller/AdminPages/StorageLocationController.php index 7a9850ab..bd4259e8 100644 --- a/src/Controller/AdminPages/StorelocationController.php +++ b/src/Controller/AdminPages/StorageLocationController.php @@ -22,9 +22,9 @@ declare(strict_types=1); namespace App\Controller\AdminPages; -use App\Entity\Attachments\StorelocationAttachment; -use App\Entity\Parameters\StorelocationParameter; -use App\Entity\Parts\Storelocation; +use App\Entity\Attachments\StorageLocationAttachment; +use App\Entity\Parameters\StorageLocationParameter; +use App\Entity\Parts\StorageLocation; use App\Form\AdminPages\StorelocationAdminForm; use App\Services\ImportExportSystem\EntityExporter; use App\Services\ImportExportSystem\EntityImporter; @@ -39,24 +39,24 @@ use Symfony\Component\Routing\Annotation\Route; * @see \App\Tests\Controller\AdminPages\StorelocationControllerTest */ #[Route(path: '/store_location')] -class StorelocationController extends BaseAdminController +class StorageLocationController extends BaseAdminController { - protected string $entity_class = Storelocation::class; + protected string $entity_class = StorageLocation::class; protected string $twig_template = 'admin/storelocation_admin.html.twig'; protected string $form_class = StorelocationAdminForm::class; protected string $route_base = 'store_location'; - protected string $attachment_class = StorelocationAttachment::class; - protected ?string $parameter_class = StorelocationParameter::class; + protected string $attachment_class = StorageLocationAttachment::class; + protected ?string $parameter_class = StorageLocationParameter::class; #[Route(path: '/{id}', name: 'store_location_delete', methods: ['DELETE'])] - public function delete(Request $request, Storelocation $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse + public function delete(Request $request, StorageLocation $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse { return $this->_delete($request, $entity, $recursionHelper); } #[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'store_location_edit')] #[Route(path: '/{id}', requirements: ['id' => '\d+'])] - public function edit(Storelocation $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response + public function edit(StorageLocation $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response { return $this->_edit($entity, $request, $em, $timestamp); } @@ -64,7 +64,7 @@ class StorelocationController extends BaseAdminController #[Route(path: '/new', name: 'store_location_new')] #[Route(path: '/{id}/clone', name: 'store_location_clone')] #[Route(path: '/')] - public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?Storelocation $entity = null): Response + public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?StorageLocation $entity = null): Response { return $this->_new($request, $em, $importer, $entity); } @@ -76,7 +76,7 @@ class StorelocationController extends BaseAdminController } #[Route(path: '/{id}/export', name: 'store_location_export')] - public function exportEntity(Storelocation $entity, EntityExporter $exporter, Request $request): Response + public function exportEntity(StorageLocation $entity, EntityExporter $exporter, Request $request): Response { return $this->_exportEntity($entity, $exporter, $request); } diff --git a/src/Controller/PartController.php b/src/Controller/PartController.php index 0537fd6a..5a965401 100644 --- a/src/Controller/PartController.php +++ b/src/Controller/PartController.php @@ -28,7 +28,7 @@ use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Orderdetail; use App\Entity\ProjectSystem\Project; @@ -201,8 +201,8 @@ class PartController extends AbstractController } $store_id = $request->get('storelocation', null); - $storelocation = $store_id ? $em->find(Storelocation::class, $store_id) : null; - if ($storelocation instanceof Storelocation && $new_part->getPartLots()->isEmpty()) { + $storelocation = $store_id ? $em->find(StorageLocation::class, $store_id) : null; + if ($storelocation instanceof StorageLocation && $new_part->getPartLots()->isEmpty()) { $partLot = new PartLot(); $partLot->setStorageLocation($storelocation); $partLot->setInstockUnknown(true); diff --git a/src/Controller/PartListsController.php b/src/Controller/PartListsController.php index 90005447..57d4b0a5 100644 --- a/src/Controller/PartListsController.php +++ b/src/Controller/PartListsController.php @@ -29,7 +29,7 @@ use App\DataTables\PartsDataTable; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Exceptions\InvalidRegexException; use App\Form\Filters\PartFilterType; @@ -214,7 +214,7 @@ class PartListsController extends AbstractController } #[Route(path: '/store_location/{id}/parts', name: 'part_list_store_location')] - public function showStorelocation(Storelocation $storelocation, Request $request): Response + public function showStorelocation(StorageLocation $storelocation, Request $request): Response { $this->denyAccessUnlessGranted('@storelocations.read'); @@ -226,7 +226,7 @@ class PartListsController extends AbstractController $this->disableFormFieldAfterCreation($filterForm->get('storelocation')->get('value')); }, [ 'entity' => $storelocation, - 'repo' => $this->entityManager->getRepository(Storelocation::class), + 'repo' => $this->entityManager->getRepository(StorageLocation::class), ] ); } diff --git a/src/Controller/TreeController.php b/src/Controller/TreeController.php index e7ce0b72..728b8bb7 100644 --- a/src/Controller/TreeController.php +++ b/src/Controller/TreeController.php @@ -27,7 +27,7 @@ use App\Entity\ProjectSystem\Project; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Services\Trees\ToolsTreeBuilder; use App\Services\Trees\TreeViewGenerator; @@ -80,10 +80,10 @@ class TreeController extends AbstractController #[Route(path: '/location/{id}', name: 'tree_location')] #[Route(path: '/locations', name: 'tree_location_root')] - public function locationTree(?Storelocation $location = null): JsonResponse + public function locationTree(?StorageLocation $location = null): JsonResponse { if ($this->isGranted('@parts.read') && $this->isGranted('@storelocations.read')) { - $tree = $this->treeGenerator->getTreeView(Storelocation::class, $location, 'list_parts_root'); + $tree = $this->treeGenerator->getTreeView(StorageLocation::class, $location, 'list_parts_root'); } else { return new JsonResponse("Access denied", Response::HTTP_FORBIDDEN); } diff --git a/src/Controller/TypeaheadController.php b/src/Controller/TypeaheadController.php index 3c0d76e9..45cdaff4 100644 --- a/src/Controller/TypeaheadController.php +++ b/src/Controller/TypeaheadController.php @@ -34,7 +34,7 @@ use App\Entity\Parameters\GroupParameter; use App\Entity\Parameters\ManufacturerParameter; use App\Entity\Parameters\MeasurementUnitParameter; use App\Entity\Parameters\PartParameter; -use App\Entity\Parameters\StorelocationParameter; +use App\Entity\Parameters\StorageLocationParameter; use App\Entity\Parameters\SupplierParameter; use App\Entity\Parts\Part; use App\Entity\PriceInformations\Currency; @@ -102,7 +102,7 @@ class TypeaheadController extends AbstractController 'device' => ProjectParameter::class, 'footprint' => FootprintParameter::class, 'manufacturer' => ManufacturerParameter::class, - 'storelocation' => StorelocationParameter::class, + 'storelocation' => StorageLocationParameter::class, 'supplier' => SupplierParameter::class, 'attachment_type' => AttachmentTypeParameter::class, 'group' => GroupParameter::class, diff --git a/src/DataFixtures/DataStructureFixtures.php b/src/DataFixtures/DataStructureFixtures.php index a2043bdb..5b2545a5 100644 --- a/src/DataFixtures/DataStructureFixtures.php +++ b/src/DataFixtures/DataStructureFixtures.php @@ -29,7 +29,7 @@ use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\UserSystem\User; use Doctrine\Bundle\FixturesBundle\Fixture; @@ -51,7 +51,7 @@ class DataStructureFixtures extends Fixture implements DependentFixtureInterface { //Reset autoincrement $types = [AttachmentType::class, Project::class, Category::class, Footprint::class, Manufacturer::class, - MeasurementUnit::class, Storelocation::class, Supplier::class,]; + MeasurementUnit::class, StorageLocation::class, Supplier::class,]; foreach ($types as $type) { $this->createNodesForClass($type, $manager); diff --git a/src/DataFixtures/PartFixtures.php b/src/DataFixtures/PartFixtures.php index 477d0dd3..a86bb348 100644 --- a/src/DataFixtures/PartFixtures.php +++ b/src/DataFixtures/PartFixtures.php @@ -49,7 +49,7 @@ use App\Entity\Parts\Manufacturer; use App\Entity\Parts\ManufacturingStatus; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Orderdetail; use App\Entity\PriceInformations\Pricedetail; @@ -94,14 +94,14 @@ class PartFixtures extends Fixture implements DependentFixtureInterface $part->setCategory($manager->find(Category::class, 1)); $partLot1 = new PartLot(); $partLot1->setAmount(1.0); - $partLot1->setStorageLocation($manager->find(Storelocation::class, 1)); + $partLot1->setStorageLocation($manager->find(StorageLocation::class, 1)); $part->addPartLot($partLot1); $partLot2 = new PartLot(); $partLot2->setExpirationDate(new DateTime()); $partLot2->setComment('Test'); $partLot2->setNeedsRefill(true); - $partLot2->setStorageLocation($manager->find(Storelocation::class, 3)); + $partLot2->setStorageLocation($manager->find(StorageLocation::class, 3)); $part->addPartLot($partLot2); $orderdetail = new Orderdetail(); diff --git a/src/DataTables/Filters/PartFilter.php b/src/DataTables/Filters/PartFilter.php index 3d03f00c..03c031a2 100644 --- a/src/DataTables/Filters/PartFilter.php +++ b/src/DataTables/Filters/PartFilter.php @@ -37,7 +37,7 @@ use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\UserSystem\User; use App\Services\Trees\NodesListBuilder; @@ -118,7 +118,7 @@ class PartFilter implements FilterInterface $this->lotCount = new IntConstraint('COUNT(partLots)'); $this->lessThanDesired = new LessThanDesiredConstraint(); - $this->storelocation = new EntityConstraint($nodesListBuilder, Storelocation::class, 'partLots.storage_location'); + $this->storelocation = new EntityConstraint($nodesListBuilder, StorageLocation::class, 'partLots.storage_location'); $this->lotNeedsRefill = new BooleanConstraint('partLots.needs_refill'); $this->lotUnknownAmount = new BooleanConstraint('partLots.instock_unknown'); $this->lotExpirationDate = new DateTimeConstraint('partLots.expiration_date'); diff --git a/src/DataTables/PartsDataTable.php b/src/DataTables/PartsDataTable.php index eac739f8..b41bf6ef 100644 --- a/src/DataTables/PartsDataTable.php +++ b/src/DataTables/PartsDataTable.php @@ -34,7 +34,7 @@ use Doctrine\ORM\Tools\Pagination\Paginator; use Omines\DataTablesBundle\Adapter\Doctrine\Event\ORMAdapterQueryEvent; use Omines\DataTablesBundle\Adapter\Doctrine\ORMAdapterEvents; use Symfony\Bundle\SecurityBundle\Security; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\DataTables\Column\EntityColumn; use App\DataTables\Column\IconLinkColumn; use App\DataTables\Column\LocaleDateTimeColumn; @@ -147,7 +147,7 @@ final class PartsDataTable implements DataTableTypeInterface $tmp = []; foreach ($context->getPartLots() as $lot) { //Ignore lots without storelocation - if (!$lot->getStorageLocation() instanceof Storelocation) { + if (!$lot->getStorageLocation() instanceof StorageLocation) { continue; } $tmp[] = sprintf( diff --git a/src/Entity/Attachments/Attachment.php b/src/Entity/Attachments/Attachment.php index f65ec913..098300a8 100644 --- a/src/Entity/Attachments/Attachment.php +++ b/src/Entity/Attachments/Attachment.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Attachments; +use App\Entity\Parts\PartTraits\ProjectTrait; use App\Repository\AttachmentRepository; use App\EntityListeners\AttachmentDeleteListener; use Doctrine\DBAL\Types\Types; @@ -42,7 +43,12 @@ use LogicException; #[ORM\Entity(repositoryClass: AttachmentRepository::class)] #[ORM\InheritanceType('SINGLE_TABLE')] #[ORM\DiscriminatorColumn(name: 'class_name', type: 'string')] -#[ORM\DiscriminatorMap(['PartDB\Part' => 'PartAttachment', 'Part' => 'PartAttachment', 'PartDB\Device' => 'ProjectAttachment', 'Device' => 'ProjectAttachment', 'AttachmentType' => 'AttachmentTypeAttachment', 'Category' => 'CategoryAttachment', 'Footprint' => 'FootprintAttachment', 'Manufacturer' => 'ManufacturerAttachment', 'Currency' => 'CurrencyAttachment', 'Group' => 'GroupAttachment', 'MeasurementUnit' => 'MeasurementUnitAttachment', 'Storelocation' => 'StorelocationAttachment', 'Supplier' => 'SupplierAttachment', 'User' => 'UserAttachment', 'LabelProfile' => 'LabelAttachment'])] +#[ORM\DiscriminatorMap(['PartDB\Part' => PartAttachment::class, 'Part' => PartAttachment::class, + 'PartDB\Device' => ProjectAttachment::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, + 'Storelocation' => StorageLocationAttachment::class, 'Supplier' => SupplierAttachment::class, + 'User' => UserAttachment::class, 'LabelProfile' => LabelAttachment::class])] #[ORM\EntityListeners([AttachmentDeleteListener::class])] #[ORM\Table(name: '`attachments`')] #[ORM\Index(name: 'attachments_idx_id_element_id_class_name', columns: ['id', 'element_id', 'class_name'])] diff --git a/src/Entity/Attachments/StorelocationAttachment.php b/src/Entity/Attachments/StorageLocationAttachment.php similarity index 77% rename from src/Entity/Attachments/StorelocationAttachment.php rename to src/Entity/Attachments/StorageLocationAttachment.php index 7772269d..301c202f 100644 --- a/src/Entity/Attachments/StorelocationAttachment.php +++ b/src/Entity/Attachments/StorageLocationAttachment.php @@ -22,24 +22,24 @@ declare(strict_types=1); namespace App\Entity\Attachments; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** * An attachment attached to a measurement unit element. - * @extends Attachment + * @extends Attachment */ #[UniqueEntity(['name', 'attachment_type', 'element'])] #[ORM\Entity] -class StorelocationAttachment extends Attachment +class StorageLocationAttachment extends Attachment { - final public const ALLOWED_ELEMENT_CLASS = Storelocation::class; + final public const ALLOWED_ELEMENT_CLASS = StorageLocation::class; /** - * @var Storelocation|null the element this attachment is associated with + * @var StorageLocation|null the element this attachment is associated with */ - #[ORM\ManyToOne(targetEntity: Storelocation::class, inversedBy: 'attachments')] + #[ORM\ManyToOne(targetEntity: StorageLocation::class, inversedBy: 'attachments')] #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] protected ?AttachmentContainingDBElement $element = null; } diff --git a/src/Entity/Base/AbstractDBElement.php b/src/Entity/Base/AbstractDBElement.php index 7fceccf4..871a22d0 100644 --- a/src/Entity/Base/AbstractDBElement.php +++ b/src/Entity/Base/AbstractDBElement.php @@ -34,7 +34,7 @@ use App\Entity\Attachments\ManufacturerAttachment; use App\Entity\Attachments\MeasurementUnitAttachment; use App\Entity\Attachments\PartAttachment; use App\Entity\Attachments\ProjectAttachment; -use App\Entity\Attachments\StorelocationAttachment; +use App\Entity\Attachments\StorageLocationAttachment; use App\Entity\Attachments\SupplierAttachment; use App\Entity\Attachments\UserAttachment; use App\Entity\Parameters\AbstractParameter; @@ -46,7 +46,7 @@ use App\Entity\UserSystem\Group; use App\Entity\Parts\Manufacturer; use App\Entity\PriceInformations\Orderdetail; use App\Entity\Parts\Part; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\PartLot; use App\Entity\PriceInformations\Currency; use App\Entity\Parts\MeasurementUnit; @@ -67,7 +67,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, 'project_attachment' => ProjectAttachment::class, 'storelocation_attachment' => StorelocationAttachment::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' => 'App\Entity\PriceInformation\Pricedetail', 'storelocation' => Storelocation::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' => 'App\Entity\PriceInformation\Pricedetail', '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/LabelSystem/LabelSupportedElement.php b/src/Entity/LabelSystem/LabelSupportedElement.php index 99bac6c9..5007de7f 100644 --- a/src/Entity/LabelSystem/LabelSupportedElement.php +++ b/src/Entity/LabelSystem/LabelSupportedElement.php @@ -22,7 +22,7 @@ namespace App\Entity\LabelSystem; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; enum LabelSupportedElement: string { @@ -39,7 +39,7 @@ enum LabelSupportedElement: string return match ($this) { self::PART => Part::class, self::PART_LOT => PartLot::class, - self::STORELOCATION => Storelocation::class, + self::STORELOCATION => StorageLocation::class, }; } } \ No newline at end of file diff --git a/src/Entity/LogSystem/AbstractLogEntry.php b/src/Entity/LogSystem/AbstractLogEntry.php index 1041cd6d..bfdcd4c7 100644 --- a/src/Entity/LogSystem/AbstractLogEntry.php +++ b/src/Entity/LogSystem/AbstractLogEntry.php @@ -36,7 +36,7 @@ use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; use App\Entity\PriceInformations\Orderdetail; diff --git a/src/Entity/LogSystem/CollectionElementDeleted.php b/src/Entity/LogSystem/CollectionElementDeleted.php index c3980a40..7cf4a68b 100644 --- a/src/Entity/LogSystem/CollectionElementDeleted.php +++ b/src/Entity/LogSystem/CollectionElementDeleted.php @@ -52,7 +52,7 @@ use App\Entity\Attachments\GroupAttachment; use App\Entity\Attachments\ManufacturerAttachment; use App\Entity\Attachments\MeasurementUnitAttachment; use App\Entity\Attachments\PartAttachment; -use App\Entity\Attachments\StorelocationAttachment; +use App\Entity\Attachments\StorageLocationAttachment; use App\Entity\Attachments\SupplierAttachment; use App\Entity\Attachments\UserAttachment; use App\Entity\Base\AbstractDBElement; @@ -69,14 +69,14 @@ use App\Entity\Parameters\GroupParameter; use App\Entity\Parameters\ManufacturerParameter; use App\Entity\Parameters\MeasurementUnitParameter; use App\Entity\Parameters\PartParameter; -use App\Entity\Parameters\StorelocationParameter; +use App\Entity\Parameters\StorageLocationParameter; use App\Entity\Parameters\SupplierParameter; 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\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; use App\Entity\UserSystem\Group; @@ -166,8 +166,8 @@ class CollectionElementDeleted extends AbstractLogEntry implements LogWithEventU return MeasurementUnitParameter::class; case Part::class: return PartParameter::class; - case Storelocation::class: - return StorelocationParameter::class; + case StorageLocation::class: + return StorageLocationParameter::class; case Supplier::class: return SupplierParameter::class; @@ -196,8 +196,8 @@ class CollectionElementDeleted extends AbstractLogEntry implements LogWithEventU return MeasurementUnitAttachment::class; case Part::class: return PartAttachment::class; - case Storelocation::class: - return StorelocationAttachment::class; + case StorageLocation::class: + return StorageLocationAttachment::class; case Supplier::class: return SupplierAttachment::class; case User::class: diff --git a/src/Entity/LogSystem/LogTargetType.php b/src/Entity/LogSystem/LogTargetType.php index 38d1f1ed..eb3346d8 100644 --- a/src/Entity/LogSystem/LogTargetType.php +++ b/src/Entity/LogSystem/LogTargetType.php @@ -30,7 +30,7 @@ use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; use App\Entity\PriceInformations\Orderdetail; @@ -81,7 +81,7 @@ enum LogTargetType: int self::GROUP => Group::class, self::MANUFACTURER => Manufacturer::class, self::PART => Part::class, - self::STORELOCATION => Storelocation::class, + self::STORELOCATION => StorageLocation::class, self::SUPPLIER => Supplier::class, self::PART_LOT => PartLot::class, self::CURRENCY => Currency::class, diff --git a/src/Entity/Parameters/AbstractParameter.php b/src/Entity/Parameters/AbstractParameter.php index 4a5cb40a..513ab8dc 100644 --- a/src/Entity/Parameters/AbstractParameter.php +++ b/src/Entity/Parameters/AbstractParameter.php @@ -41,6 +41,7 @@ declare(strict_types=1); namespace App\Entity\Parameters; +use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\ParameterRepository; use Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractDBElement; @@ -56,7 +57,10 @@ use function sprintf; #[ORM\Entity(repositoryClass: ParameterRepository::class)] #[ORM\InheritanceType('SINGLE_TABLE')] #[ORM\DiscriminatorColumn(name: 'type', type: 'smallint')] -#[ORM\DiscriminatorMap([0 => 'CategoryParameter', 1 => 'CurrencyParameter', 2 => 'ProjectParameter', 3 => 'FootprintParameter', 4 => 'GroupParameter', 5 => 'ManufacturerParameter', 6 => 'MeasurementUnitParameter', 7 => 'PartParameter', 8 => 'StorelocationParameter', 9 => 'SupplierParameter', 10 => 'AttachmentTypeParameter'])] +#[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 => AttachmentTypeAttachment::class])] #[ORM\Table('parameters')] #[ORM\Index(name: 'parameter_name_idx', columns: ['name'])] #[ORM\Index(name: 'parameter_group_idx', columns: ['param_group'])] diff --git a/src/Entity/Parameters/StorelocationParameter.php b/src/Entity/Parameters/StorageLocationParameter.php similarity index 87% rename from src/Entity/Parameters/StorelocationParameter.php rename to src/Entity/Parameters/StorageLocationParameter.php index 098d3a5e..fe2a1169 100644 --- a/src/Entity/Parameters/StorelocationParameter.php +++ b/src/Entity/Parameters/StorageLocationParameter.php @@ -43,20 +43,20 @@ namespace App\Entity\Parameters; use App\Repository\ParameterRepository; use App\Entity\Base\AbstractDBElement; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; #[UniqueEntity(fields: ['name', 'group', 'element'])] #[ORM\Entity(repositoryClass: ParameterRepository::class)] -class StorelocationParameter extends AbstractParameter +class StorageLocationParameter extends AbstractParameter { - final public const ALLOWED_ELEMENT_CLASS = Storelocation::class; + final public const ALLOWED_ELEMENT_CLASS = StorageLocation::class; /** - * @var Storelocation the element this para is associated with + * @var StorageLocation the element this para is associated with */ - #[ORM\ManyToOne(targetEntity: Storelocation::class, inversedBy: 'parameters')] + #[ORM\ManyToOne(targetEntity: StorageLocation::class, inversedBy: 'parameters')] #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] protected ?AbstractDBElement $element = null; } diff --git a/src/Entity/Parts/PartLot.php b/src/Entity/Parts/PartLot.php index b4f22eab..f026ddec 100644 --- a/src/Entity/Parts/PartLot.php +++ b/src/Entity/Parts/PartLot.php @@ -97,13 +97,13 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named protected ?\DateTimeInterface $expiration_date = null; /** - * @var Storelocation|null The storelocation of this lot + * @var StorageLocation|null The storelocation of this lot */ #[Groups(['simple', 'extended', 'full', 'import', 'part_lot:read', 'part_lot:write'])] - #[ORM\ManyToOne(targetEntity: Storelocation::class, fetch: 'EAGER')] + #[ORM\ManyToOne(targetEntity: StorageLocation::class, fetch: 'EAGER')] #[ORM\JoinColumn(name: 'id_store_location')] #[Selectable()] - protected ?Storelocation $storage_location = null; + protected ?StorageLocation $storage_location = null; /** * @var bool If this is set to true, the instock amount is marked as not known @@ -229,9 +229,9 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named /** * Gets the storage location, where this part lot is stored. * - * @return Storelocation|null The store location where this part is stored + * @return StorageLocation|null The store location where this part is stored */ - public function getStorageLocation(): ?Storelocation + public function getStorageLocation(): ?StorageLocation { return $this->storage_location; } @@ -239,7 +239,7 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named /** * Sets the storage location, where this part lot is stored. */ - public function setStorageLocation(?Storelocation $storage_location): self + public function setStorageLocation(?StorageLocation $storage_location): self { $this->storage_location = $storage_location; diff --git a/src/Entity/Parts/Storelocation.php b/src/Entity/Parts/StorageLocation.php similarity index 76% rename from src/Entity/Parts/Storelocation.php rename to src/Entity/Parts/StorageLocation.php index f58f807d..16915e9f 100644 --- a/src/Entity/Parts/Storelocation.php +++ b/src/Entity/Parts/StorageLocation.php @@ -22,14 +22,23 @@ declare(strict_types=1); namespace App\Entity\Parts; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Serializer\Filter\PropertyFilter; use App\Entity\Attachments\Attachment; use App\Repository\Parts\StorelocationRepository; use Doctrine\DBAL\Types\Types; use Doctrine\Common\Collections\ArrayCollection; -use App\Entity\Attachments\StorelocationAttachment; +use App\Entity\Attachments\StorageLocationAttachment; use App\Entity\Base\AbstractPartsContainingDBElement; use App\Entity\Base\AbstractStructuralDBElement; -use App\Entity\Parameters\StorelocationParameter; +use App\Entity\Parameters\StorageLocationParameter; use App\Entity\UserSystem\User; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; @@ -38,13 +47,36 @@ use Symfony\Component\Validator\Constraints as Assert; /** * This entity represents a storage location, where parts can be stored. - * @extends AbstractPartsContainingDBElement + * @extends AbstractPartsContainingDBElement */ #[ORM\Entity(repositoryClass: StorelocationRepository::class)] #[ORM\Table('`storelocations`')] #[ORM\Index(name: 'location_idx_name', columns: ['name'])] #[ORM\Index(name: 'location_idx_parent_name', columns: ['parent_id', 'name'])] -class Storelocation extends AbstractPartsContainingDBElement +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@storelocations.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['location:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['location:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiResource( + uriTemplate: '/storage_locations/{id}/children.{_format}', + operations: [ + new GetCollection(openapiContext: ['summary' => 'Retrieves the children elements of a storage location.'], + security: 'is_granted("@storelocations.read")') + ], + uriVariables: [ + 'id' => new Link(fromProperty: 'children', fromClass: Manufacturer::class) + ], + normalizationContext: ['groups' => ['location:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] +)] +#[ApiFilter(PropertyFilter::class)] +class StorageLocation extends AbstractPartsContainingDBElement { #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')] #[ORM\OrderBy(['name' => 'ASC'])] @@ -61,10 +93,10 @@ class Storelocation extends AbstractPartsContainingDBElement #[ORM\JoinColumn(name: 'storage_type_id')] protected ?MeasurementUnit $storage_type = null; - /** @var Collection + /** @var Collection */ #[Assert\Valid] - #[ORM\OneToMany(targetEntity: StorelocationParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OneToMany(targetEntity: StorageLocationParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] protected Collection $parameters; @@ -104,13 +136,13 @@ class Storelocation extends AbstractPartsContainingDBElement protected bool $part_owner_must_match = false; /** - * @var Collection + * @var Collection */ #[Assert\Valid] - #[ORM\OneToMany(targetEntity: StorelocationAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OneToMany(targetEntity: StorageLocationAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] protected Collection $attachments; - #[ORM\ManyToOne(targetEntity: StorelocationAttachment::class)] + #[ORM\ManyToOne(targetEntity: StorageLocationAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] protected ?Attachment $master_picture_attachment = null; @@ -186,7 +218,7 @@ class Storelocation extends AbstractPartsContainingDBElement /** * Sets the owner of this storage location */ - public function setOwner(?User $owner): Storelocation + public function setOwner(?User $owner): StorageLocation { $this->owner = $owner; return $this; @@ -203,7 +235,7 @@ class Storelocation extends AbstractPartsContainingDBElement /** * If this is set to true, only parts lots, which are owned by the same user as the store location are allowed to be stored here. */ - public function setPartOwnerMustMatch(bool $part_owner_must_match): Storelocation + public function setPartOwnerMustMatch(bool $part_owner_must_match): StorageLocation { $this->part_owner_must_match = $part_owner_must_match; return $this; diff --git a/src/Form/Filters/AttachmentFilterType.php b/src/Form/Filters/AttachmentFilterType.php index 92cb20b6..e6746feb 100644 --- a/src/Form/Filters/AttachmentFilterType.php +++ b/src/Form/Filters/AttachmentFilterType.php @@ -32,7 +32,7 @@ use App\Entity\Attachments\FootprintAttachment; use App\Entity\Attachments\GroupAttachment; use App\Entity\Attachments\LabelAttachment; use App\Entity\Attachments\PartAttachment; -use App\Entity\Attachments\StorelocationAttachment; +use App\Entity\Attachments\StorageLocationAttachment; use App\Entity\Attachments\SupplierAttachment; use App\Entity\Attachments\UserAttachment; use App\Entity\Parts\Manufacturer; @@ -85,7 +85,7 @@ class AttachmentFilterType extends AbstractType 'label_profile.label' => LabelAttachment::class, 'manufacturer.label' => Manufacturer::class, 'measurement_unit.label' => MeasurementUnit::class, - 'storelocation.label' => StorelocationAttachment::class, + 'storelocation.label' => StorageLocationAttachment::class, 'supplier.label' => SupplierAttachment::class, 'user.label' => UserAttachment::class, ] diff --git a/src/Form/Filters/LogFilterType.php b/src/Form/Filters/LogFilterType.php index 0d8257f4..50179869 100644 --- a/src/Form/Filters/LogFilterType.php +++ b/src/Form/Filters/LogFilterType.php @@ -49,7 +49,7 @@ use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; use App\Entity\PriceInformations\Orderdetail; diff --git a/src/Form/Filters/PartFilterType.php b/src/Form/Filters/PartFilterType.php index 0a60dc4c..5f367da2 100644 --- a/src/Form/Filters/PartFilterType.php +++ b/src/Form/Filters/PartFilterType.php @@ -29,7 +29,7 @@ use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Form\Filters\Constraints\BooleanConstraintType; use App\Form\Filters\Constraints\ChoiceConstraintType; @@ -191,7 +191,7 @@ class PartFilterType extends AbstractType */ $builder->add('storelocation', StructuralEntityConstraintType::class, [ 'label' => 'storelocation.label', - 'entity_class' => Storelocation::class + 'entity_class' => StorageLocation::class ]); $builder->add('minAmount', NumberConstraintType::class, [ diff --git a/src/Form/ParameterType.php b/src/Form/ParameterType.php index 66e664be..517bb48c 100644 --- a/src/Form/ParameterType.php +++ b/src/Form/ParameterType.php @@ -50,7 +50,7 @@ use App\Entity\Parameters\FootprintParameter; use App\Entity\Parameters\GroupParameter; use App\Entity\Parameters\ManufacturerParameter; use App\Entity\Parameters\PartParameter; -use App\Entity\Parameters\StorelocationParameter; +use App\Entity\Parameters\StorageLocationParameter; use App\Entity\Parameters\SupplierParameter; use App\Entity\Parts\MeasurementUnit; use Symfony\Component\Form\AbstractType; @@ -163,7 +163,7 @@ class ParameterType extends AbstractType GroupParameter::class => 'group', ManufacturerParameter::class => 'manufacturer', MeasurementUnit::class => 'measurement_unit', - StorelocationParameter::class => 'storelocation', + StorageLocationParameter::class => 'storelocation', SupplierParameter::class => 'supplier', ]; diff --git a/src/Form/Part/PartLotType.php b/src/Form/Part/PartLotType.php index 707caf28..ed730cbf 100644 --- a/src/Form/Part/PartLotType.php +++ b/src/Form/Part/PartLotType.php @@ -25,7 +25,7 @@ namespace App\Form\Part; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Form\Type\SIUnitType; use App\Form\Type\StructuralEntityType; use App\Form\Type\UserSelectType; @@ -54,7 +54,7 @@ class PartLotType extends AbstractType ]); $builder->add('storage_location', StructuralEntityType::class, [ - 'class' => Storelocation::class, + 'class' => StorageLocation::class, 'label' => 'part_lot.edit.location', 'required' => false, 'disable_not_selectable' => true, diff --git a/src/Form/Type/PartLotSelectType.php b/src/Form/Type/PartLotSelectType.php index 8eff5122..c68535a7 100644 --- a/src/Form/Type/PartLotSelectType.php +++ b/src/Form/Type/PartLotSelectType.php @@ -22,7 +22,7 @@ declare(strict_types=1); */ namespace App\Form\Type; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; use Doctrine\ORM\EntityRepository; @@ -46,7 +46,7 @@ class PartLotSelectType extends AbstractType $resolver->setDefaults([ 'class' => PartLot::class, - 'choice_label' => ChoiceList::label($this, static fn(PartLot $part_lot): string => ($part_lot->getStorageLocation() instanceof Storelocation ? $part_lot->getStorageLocation()->getFullPath() : '') + 'choice_label' => ChoiceList::label($this, static fn(PartLot $part_lot): string => ($part_lot->getStorageLocation() instanceof StorageLocation ? $part_lot->getStorageLocation()->getFullPath() : '') . ' (' . $part_lot->getName() . '): ' . $part_lot->getAmount()), 'query_builder' => fn(Options $options) => static fn(EntityRepository $er) => $er->createQueryBuilder('l') ->where('l.part = :part') diff --git a/src/Repository/Parts/StorelocationRepository.php b/src/Repository/Parts/StorelocationRepository.php index e6eea9a5..192d4f6d 100644 --- a/src/Repository/Parts/StorelocationRepository.php +++ b/src/Repository/Parts/StorelocationRepository.php @@ -23,7 +23,7 @@ declare(strict_types=1); namespace App\Repository\Parts; use App\Entity\Parts\Part; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Repository\AbstractPartsContainingRepository; use Doctrine\ORM\QueryBuilder; use InvalidArgumentException; @@ -37,7 +37,7 @@ class StorelocationRepository extends AbstractPartsContainingRepository */ public function getParts(object $element, array $order_by = ['name' => 'ASC']): array { - if (!$element instanceof Storelocation) { + if (!$element instanceof StorageLocation) { throw new InvalidArgumentException('$element must be an Storelocation!'); } @@ -58,7 +58,7 @@ class StorelocationRepository extends AbstractPartsContainingRepository public function getPartsCount(object $element): int { - if (!$element instanceof Storelocation) { + if (!$element instanceof StorageLocation) { throw new InvalidArgumentException('$element must be an Storelocation!'); } diff --git a/src/Security/Voter/AttachmentVoter.php b/src/Security/Voter/AttachmentVoter.php index 54f6cc70..85a7eb1f 100644 --- a/src/Security/Voter/AttachmentVoter.php +++ b/src/Security/Voter/AttachmentVoter.php @@ -35,7 +35,7 @@ use App\Entity\Attachments\ManufacturerAttachment; use App\Entity\Attachments\MeasurementUnitAttachment; use App\Entity\Attachments\PartAttachment; use App\Entity\Attachments\ProjectAttachment; -use App\Entity\Attachments\StorelocationAttachment; +use App\Entity\Attachments\StorageLocationAttachment; use App\Entity\Attachments\SupplierAttachment; use App\Entity\Attachments\UserAttachment; use RuntimeException; @@ -94,7 +94,7 @@ final class AttachmentVoter extends Voter $param = 'measurement_units'; } elseif (is_a($subject, PartAttachment::class, true)) { $param = 'parts'; - } elseif (is_a($subject, StorelocationAttachment::class, true)) { + } elseif (is_a($subject, StorageLocationAttachment::class, true)) { $param = 'storelocations'; } elseif (is_a($subject, SupplierAttachment::class, true)) { $param = 'suppliers'; diff --git a/src/Security/Voter/ParameterVoter.php b/src/Security/Voter/ParameterVoter.php index 47657055..6699b56f 100644 --- a/src/Security/Voter/ParameterVoter.php +++ b/src/Security/Voter/ParameterVoter.php @@ -35,7 +35,7 @@ use App\Entity\Parameters\GroupParameter; use App\Entity\Parameters\ManufacturerParameter; use App\Entity\Parameters\MeasurementUnitParameter; use App\Entity\Parameters\PartParameter; -use App\Entity\Parameters\StorelocationParameter; +use App\Entity\Parameters\StorageLocationParameter; use App\Entity\Parameters\SupplierParameter; use App\Entity\UserSystem\User; use App\Services\UserSystem\PermissionManager; @@ -96,7 +96,7 @@ final class ParameterVoter extends Voter $param = 'measurement_units'; } elseif (is_a($subject, PartParameter::class, true)) { $param = 'parts'; - } elseif (is_a($subject, StorelocationParameter::class, true)) { + } elseif (is_a($subject, StorageLocationParameter::class, true)) { $param = 'storelocations'; } elseif (is_a($subject, SupplierParameter::class, true)) { $param = 'suppliers'; diff --git a/src/Security/Voter/StructureVoter.php b/src/Security/Voter/StructureVoter.php index 8f4c144f..7b68215f 100644 --- a/src/Security/Voter/StructureVoter.php +++ b/src/Security/Voter/StructureVoter.php @@ -28,7 +28,7 @@ use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; use App\Entity\UserSystem\User; @@ -47,7 +47,7 @@ final class StructureVoter extends Voter Project::class => 'projects', Footprint::class => 'footprints', Manufacturer::class => 'manufacturers', - Storelocation::class => 'storelocations', + StorageLocation::class => 'storelocations', Supplier::class => 'suppliers', Currency::class => 'currencies', MeasurementUnit::class => 'measurement_units', diff --git a/src/Serializer/PartNormalizer.php b/src/Serializer/PartNormalizer.php index b453a58e..509809a7 100644 --- a/src/Serializer/PartNormalizer.php +++ b/src/Serializer/PartNormalizer.php @@ -24,7 +24,7 @@ namespace App\Serializer; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Orderdetail; use App\Entity\PriceInformations\Pricedetail; @@ -148,7 +148,7 @@ class PartNormalizer implements NormalizerInterface, DenormalizerInterface } if (isset($data['storelocation']) && $data['storelocation'] !== "") { - $location = $this->locationDenormalizer->denormalize($data['storelocation'], Storelocation::class, $format, $context); + $location = $this->locationDenormalizer->denormalize($data['storelocation'], StorageLocation::class, $format, $context); $partLot->setStorageLocation($location); } diff --git a/src/Services/Attachments/AttachmentSubmitHandler.php b/src/Services/Attachments/AttachmentSubmitHandler.php index b8b15907..ac7ec4b5 100644 --- a/src/Services/Attachments/AttachmentSubmitHandler.php +++ b/src/Services/Attachments/AttachmentSubmitHandler.php @@ -35,7 +35,7 @@ use App\Entity\Attachments\GroupAttachment; use App\Entity\Attachments\ManufacturerAttachment; use App\Entity\Attachments\MeasurementUnitAttachment; use App\Entity\Attachments\PartAttachment; -use App\Entity\Attachments\StorelocationAttachment; +use App\Entity\Attachments\StorageLocationAttachment; use App\Entity\Attachments\SupplierAttachment; use App\Entity\Attachments\UserAttachment; use App\Exceptions\AttachmentDownloadException; @@ -81,7 +81,7 @@ class AttachmentSubmitHandler GroupAttachment::class => 'group', ManufacturerAttachment::class => 'manufacturer', MeasurementUnitAttachment::class => 'measurement_unit', - StorelocationAttachment::class => 'storelocation', + StorageLocationAttachment::class => 'storelocation', SupplierAttachment::class => 'supplier', UserAttachment::class => 'user', LabelAttachment::class => 'label_profile', diff --git a/src/Services/Attachments/PartPreviewGenerator.php b/src/Services/Attachments/PartPreviewGenerator.php index 8fe1c72c..ba6e5db0 100644 --- a/src/Services/Attachments/PartPreviewGenerator.php +++ b/src/Services/Attachments/PartPreviewGenerator.php @@ -25,7 +25,7 @@ namespace App\Services\Attachments; use App\Entity\Parts\Footprint; use App\Entity\ProjectSystem\Project; use App\Entity\Parts\Category; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Manufacturer; use App\Entity\Attachments\Attachment; @@ -88,7 +88,7 @@ class PartPreviewGenerator } foreach ($part->getPartLots() as $lot) { - if ($lot->getStorageLocation() instanceof Storelocation) { + if ($lot->getStorageLocation() instanceof StorageLocation) { $attachment = $lot->getStorageLocation()->getMasterPictureAttachment(); if ($this->isAttachmentValidPicture($attachment)) { $list[] = $attachment; diff --git a/src/Services/ElementTypeNameGenerator.php b/src/Services/ElementTypeNameGenerator.php index f32677eb..b9653d13 100644 --- a/src/Services/ElementTypeNameGenerator.php +++ b/src/Services/ElementTypeNameGenerator.php @@ -37,7 +37,7 @@ use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; use App\Entity\PriceInformations\Orderdetail; @@ -71,7 +71,7 @@ class ElementTypeNameGenerator MeasurementUnit::class => $this->translator->trans('measurement_unit.label'), Part::class => $this->translator->trans('part.label'), PartLot::class => $this->translator->trans('part_lot.label'), - Storelocation::class => $this->translator->trans('storelocation.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'), diff --git a/src/Services/EntityURLGenerator.php b/src/Services/EntityURLGenerator.php index 26807f69..7705d305 100644 --- a/src/Services/EntityURLGenerator.php +++ b/src/Services/EntityURLGenerator.php @@ -35,7 +35,7 @@ use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; use App\Entity\PriceInformations\Orderdetail; @@ -102,7 +102,7 @@ class EntityURLGenerator Project::class => 'project_edit', Supplier::class => 'supplier_edit', Manufacturer::class => 'manufacturer_edit', - Storelocation::class => 'store_location_edit', + StorageLocation::class => 'store_location_edit', Footprint::class => 'footprint_edit', User::class => 'user_edit', Currency::class => 'currency_edit', @@ -199,7 +199,7 @@ class EntityURLGenerator Project::class => 'project_info', Supplier::class => 'supplier_edit', Manufacturer::class => 'manufacturer_edit', - Storelocation::class => 'store_location_edit', + StorageLocation::class => 'store_location_edit', Footprint::class => 'footprint_edit', User::class => 'user_edit', Currency::class => 'currency_edit', @@ -229,7 +229,7 @@ class EntityURLGenerator Project::class => 'project_edit', Supplier::class => 'supplier_edit', Manufacturer::class => 'manufacturer_edit', - Storelocation::class => 'store_location_edit', + StorageLocation::class => 'store_location_edit', Footprint::class => 'footprint_edit', User::class => 'user_edit', Currency::class => 'currency_edit', @@ -260,7 +260,7 @@ class EntityURLGenerator Project::class => 'project_new', Supplier::class => 'supplier_new', Manufacturer::class => 'manufacturer_new', - Storelocation::class => 'store_location_new', + StorageLocation::class => 'store_location_new', Footprint::class => 'footprint_new', User::class => 'user_new', Currency::class => 'currency_new', @@ -291,7 +291,7 @@ class EntityURLGenerator Project::class => 'device_clone', Supplier::class => 'supplier_clone', Manufacturer::class => 'manufacturer_clone', - Storelocation::class => 'store_location_clone', + StorageLocation::class => 'store_location_clone', Footprint::class => 'footprint_clone', User::class => 'user_clone', Currency::class => 'currency_clone', @@ -321,7 +321,7 @@ class EntityURLGenerator Footprint::class => 'part_list_footprint', Manufacturer::class => 'part_list_manufacturer', Supplier::class => 'part_list_supplier', - Storelocation::class => 'part_list_store_location', + StorageLocation::class => 'part_list_store_location', ]; return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]); @@ -336,7 +336,7 @@ class EntityURLGenerator Project::class => 'project_delete', Supplier::class => 'supplier_delete', Manufacturer::class => 'manufacturer_delete', - Storelocation::class => 'store_location_delete', + StorageLocation::class => 'store_location_delete', Footprint::class => 'footprint_delete', User::class => 'user_delete', Currency::class => 'currency_delete', diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKDatastructureImporter.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKDatastructureImporter.php index 4f9acbca..e6929c7b 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/PKDatastructureImporter.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKDatastructureImporter.php @@ -25,7 +25,7 @@ namespace App\Services\ImportExportSystem\PartKeeprImporter; use App\Doctrine\Purger\ResetAutoIncrementORMPurger; use App\Entity\Attachments\FootprintAttachment; use App\Entity\Attachments\ManufacturerAttachment; -use App\Entity\Attachments\StorelocationAttachment; +use App\Entity\Attachments\StorageLocationAttachment; use App\Entity\Base\AbstractDBElement; use App\Entity\Base\AbstractStructuralDBElement; use App\Entity\Contracts\TimeStampableInterface; @@ -36,7 +36,7 @@ use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadataInfo; @@ -263,9 +263,9 @@ class PKDatastructureImporter public function importStorelocations(array $data): int { - $count = $this->importElementsWithCategory($data, Storelocation::class, 'storagelocation'); + $count = $this->importElementsWithCategory($data, StorageLocation::class, 'storagelocation'); - $this->importAttachments($data, 'storagelocationimage', Storelocation::class, 'storageLocation_id', StorelocationAttachment::class); + $this->importAttachments($data, 'storagelocationimage', StorageLocation::class, 'storageLocation_id', StorageLocationAttachment::class); return $count; } diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php index ab00ba5b..fdb51633 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php @@ -30,7 +30,7 @@ use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; use App\Entity\PriceInformations\Orderdetail; @@ -93,7 +93,7 @@ class PKPartImporter //Create a part lot to store the stock level and location $lot = new PartLot(); $lot->setAmount((float) ($part['stockLevel'] ?? 0)); - $this->setAssociationField($lot, 'storage_location', Storelocation::class, $part['storageLocation_id']); + $this->setAssociationField($lot, 'storage_location', StorageLocation::class, $part['storageLocation_id']); $entity->addPartLot($lot); //For partCondition, productionsRemarks and Status, create a custom parameter diff --git a/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php b/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php index 5acf98f1..33743e4f 100644 --- a/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php +++ b/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php @@ -44,7 +44,7 @@ namespace App\Services\LabelSystem\Barcodes; use App\Entity\Base\AbstractDBElement; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use InvalidArgumentException; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; @@ -56,13 +56,13 @@ final class BarcodeContentGenerator public const PREFIX_MAP = [ Part::class => 'P', PartLot::class => 'L', - Storelocation::class => 'S', + StorageLocation::class => 'S', ]; private const URL_MAP = [ Part::class => 'part', PartLot::class => 'lot', - Storelocation::class => 'location', + StorageLocation::class => 'location', ]; public function __construct(private readonly UrlGeneratorInterface $urlGenerator) diff --git a/src/Services/LabelSystem/LabelExampleElementsGenerator.php b/src/Services/LabelSystem/LabelExampleElementsGenerator.php index 61cbcc4a..c9cdbb04 100644 --- a/src/Services/LabelSystem/LabelExampleElementsGenerator.php +++ b/src/Services/LabelSystem/LabelExampleElementsGenerator.php @@ -49,7 +49,7 @@ use App\Entity\Parts\Manufacturer; use App\Entity\Parts\ManufacturingStatus; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\UserSystem\User; use DateTime; use InvalidArgumentException; @@ -98,23 +98,23 @@ final class LabelExampleElementsGenerator $lot->setDescription('Example Lot'); $lot->setComment('Lot comment'); $lot->setExpirationDate(new DateTime('+1 days')); - $lot->setStorageLocation($this->getStructuralData(Storelocation::class)); + $lot->setStorageLocation($this->getStructuralData(StorageLocation::class)); $lot->setAmount(123); $lot->setOwner($this->getUser()); return $lot; } - private function getStorelocation(): Storelocation + private function getStorelocation(): StorageLocation { - $storelocation = new Storelocation(); + $storelocation = new StorageLocation(); $storelocation->setName('Location 1'); $storelocation->setComment('Example comment'); $storelocation->updateTimestamps(); $storelocation->setOwner($this->getUser()); - $parent = new Storelocation(); + $parent = new StorageLocation(); $parent->setName('Parent'); $storelocation->setParent($parent); diff --git a/src/Services/LabelSystem/LabelGenerator.php b/src/Services/LabelSystem/LabelGenerator.php index f16b135a..3128d5a2 100644 --- a/src/Services/LabelSystem/LabelGenerator.php +++ b/src/Services/LabelSystem/LabelGenerator.php @@ -44,7 +44,7 @@ namespace App\Services\LabelSystem; use App\Entity\LabelSystem\LabelOptions; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use Dompdf\Dompdf; use InvalidArgumentException; use Jbtronics\DompdfFontLoaderBundle\Services\DompdfFactoryInterface; diff --git a/src/Services/LabelSystem/PlaceholderProviders/PartLotProvider.php b/src/Services/LabelSystem/PlaceholderProviders/PartLotProvider.php index fb9447ba..946b4892 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/PartLotProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/PartLotProvider.php @@ -41,7 +41,7 @@ declare(strict_types=1); namespace App\Services\LabelSystem\PlaceholderProviders; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\UserSystem\User; use App\Entity\Parts\PartLot; use App\Services\Formatters\AmountFormatter; @@ -95,11 +95,11 @@ final class PartLotProvider implements PlaceholderProviderInterface } if ('[[LOCATION]]' === $placeholder) { - return $label_target->getStorageLocation() instanceof Storelocation ? $label_target->getStorageLocation()->getName() : ''; + return $label_target->getStorageLocation() instanceof StorageLocation ? $label_target->getStorageLocation()->getName() : ''; } if ('[[LOCATION_FULL]]' === $placeholder) { - return $label_target->getStorageLocation() instanceof Storelocation ? $label_target->getStorageLocation()->getFullPath() : ''; + return $label_target->getStorageLocation() instanceof StorageLocation ? $label_target->getStorageLocation()->getFullPath() : ''; } if ('[[OWNER]]' === $placeholder) { diff --git a/src/Services/LabelSystem/PlaceholderProviders/StorelocationProvider.php b/src/Services/LabelSystem/PlaceholderProviders/StorelocationProvider.php index 95654eca..4b4d8dcd 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/StorelocationProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/StorelocationProvider.php @@ -23,13 +23,13 @@ declare(strict_types=1); namespace App\Services\LabelSystem\PlaceholderProviders; use App\Entity\UserSystem\User; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; class StorelocationProvider implements PlaceholderProviderInterface { public function replace(string $placeholder, object $label_target, array $options = []): ?string { - if ($label_target instanceof Storelocation) { + if ($label_target instanceof StorageLocation) { if ('[[OWNER]]' === $placeholder) { return $label_target->getOwner() instanceof User ? $label_target->getOwner()->getFullName() : ''; } diff --git a/src/Services/LabelSystem/SandboxedTwigProvider.php b/src/Services/LabelSystem/SandboxedTwigProvider.php index a0426cd1..a6a54118 100644 --- a/src/Services/LabelSystem/SandboxedTwigProvider.php +++ b/src/Services/LabelSystem/SandboxedTwigProvider.php @@ -54,7 +54,7 @@ use App\Entity\Parameters\AbstractParameter; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; use App\Entity\PriceInformations\Orderdetail; @@ -101,7 +101,7 @@ final class SandboxedTwigProvider MeasurementUnit::class => ['getUnit', 'isInteger', 'useSIPrefix'], PartLot::class => ['isExpired', 'getDescription', 'getComment', 'getExpirationDate', 'getStorageLocation', 'getPart', 'isInstockUnknown', 'getAmount', 'getNeedsRefill', ], - Storelocation::class => ['isFull', 'isOnlySinglePart', 'isLimitToExistingParts', 'getStorageType'], + StorageLocation::class => ['isFull', 'isOnlySinglePart', 'isLimitToExistingParts', 'getStorageType'], Supplier::class => ['getShippingCosts', 'getDefaultCurrency'], Part::class => ['isNeedsReview', 'getTags', 'getMass', 'getDescription', 'isFavorite', 'getCategory', 'getFootprint', 'getPartLots', 'getPartUnit', 'useFloatAmount', 'getMinAmount', 'getAmountSum', diff --git a/src/Services/Parts/PartLotWithdrawAddHelper.php b/src/Services/Parts/PartLotWithdrawAddHelper.php index 8e2d2d28..8086081f 100644 --- a/src/Services/Parts/PartLotWithdrawAddHelper.php +++ b/src/Services/Parts/PartLotWithdrawAddHelper.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace App\Services\Parts; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\LogSystem\PartStockChangedLogEntry; use App\Entity\Parts\PartLot; use App\Services\LogSystem\EventCommentHelper; @@ -30,7 +30,7 @@ final class PartLotWithdrawAddHelper } //So far all other restrictions are defined at the storelocation level - if(!$partLot->getStorageLocation() instanceof Storelocation) { + if(!$partLot->getStorageLocation() instanceof StorageLocation) { return true; } //We can not add parts if the storage location of the lot is marked as full diff --git a/src/Services/Tools/StatisticsHelper.php b/src/Services/Tools/StatisticsHelper.php index 0ee736f9..c1cace21 100644 --- a/src/Services/Tools/StatisticsHelper.php +++ b/src/Services/Tools/StatisticsHelper.php @@ -49,7 +49,7 @@ use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; use App\Repository\AttachmentRepository; @@ -113,7 +113,7 @@ class StatisticsHelper 'footprint' => Footprint::class, 'manufacturer' => Manufacturer::class, 'measurement_unit' => MeasurementUnit::class, - 'storelocation' => Storelocation::class, + 'storelocation' => StorageLocation::class, 'supplier' => Supplier::class, 'currency' => Currency::class, ]; diff --git a/src/Services/Trees/ToolsTreeBuilder.php b/src/Services/Trees/ToolsTreeBuilder.php index b0fafb4f..3e5763ca 100644 --- a/src/Services/Trees/ToolsTreeBuilder.php +++ b/src/Services/Trees/ToolsTreeBuilder.php @@ -31,7 +31,7 @@ use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; use App\Entity\UserSystem\Group; @@ -182,7 +182,7 @@ class ToolsTreeBuilder $this->urlGenerator->generate('manufacturer_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-industry'); } - if ($this->security->isGranted('read', new Storelocation())) { + if ($this->security->isGranted('read', new StorageLocation())) { $nodes[] = (new TreeViewNode( $this->translator->trans('tree.tools.edit.storelocation'), $this->urlGenerator->generate('store_location_new') diff --git a/src/Services/Trees/TreeViewGenerator.php b/src/Services/Trees/TreeViewGenerator.php index f3b0d4ea..056572a5 100644 --- a/src/Services/Trees/TreeViewGenerator.php +++ b/src/Services/Trees/TreeViewGenerator.php @@ -29,7 +29,7 @@ use App\Entity\ProjectSystem\Project; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Helpers\Trees\TreeViewNode; use App\Helpers\Trees\TreeViewNodeIterator; @@ -142,7 +142,7 @@ class TreeViewGenerator { return match ($class) { Category::class => $this->translator->trans('category.labelp'), - Storelocation::class => $this->translator->trans('storelocation.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'), @@ -156,7 +156,7 @@ class TreeViewGenerator $icon = "fa-fw fa-treeview fa-solid "; return match ($class) { Category::class => $icon . 'fa-tags', - Storelocation::class => $icon . 'fa-cube', + StorageLocation::class => $icon . 'fa-cube', Footprint::class => $icon . 'fa-microchip', Manufacturer::class => $icon . 'fa-industry', Supplier::class => $icon . 'fa-truck', diff --git a/src/Twig/EntityExtension.php b/src/Twig/EntityExtension.php index ee53847d..762ebb09 100644 --- a/src/Twig/EntityExtension.php +++ b/src/Twig/EntityExtension.php @@ -31,7 +31,7 @@ use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; use App\Entity\UserSystem\Group; @@ -104,7 +104,7 @@ final class EntityExtension extends AbstractExtension $map = [ Part::class => 'part', Footprint::class => 'footprint', - Storelocation::class => 'storelocation', + StorageLocation::class => 'storelocation', Manufacturer::class => 'manufacturer', Category::class => 'category', Project::class => 'device', diff --git a/src/Validator/Constraints/ValidPartLotValidator.php b/src/Validator/Constraints/ValidPartLotValidator.php index 4f988362..880bd5b3 100644 --- a/src/Validator/Constraints/ValidPartLotValidator.php +++ b/src/Validator/Constraints/ValidPartLotValidator.php @@ -23,7 +23,7 @@ declare(strict_types=1); namespace App\Validator\Constraints; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Form\Exception\UnexpectedTypeException; @@ -53,8 +53,8 @@ class ValidPartLotValidator extends ConstraintValidator } //We can only validate the values if we know the storelocation - if ($value->getStorageLocation() instanceof Storelocation) { - $repo = $this->em->getRepository(Storelocation::class); + if ($value->getStorageLocation() instanceof StorageLocation) { + $repo = $this->em->getRepository(StorageLocation::class); //We can only determine associated parts, if the part have an ID //When the storage location is new (no ID), we can just assume there are no other parts if (null !== $value->getID() && $value->getStorageLocation()->getID()) { diff --git a/tests/Controller/AdminPages/StorelocationControllerTest.php b/tests/Controller/AdminPages/StorelocationControllerTest.php index fd8d1e8c..65a9fe34 100644 --- a/tests/Controller/AdminPages/StorelocationControllerTest.php +++ b/tests/Controller/AdminPages/StorelocationControllerTest.php @@ -22,7 +22,7 @@ declare(strict_types=1); namespace App\Tests\Controller\AdminPages; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; /** * @group slow @@ -31,5 +31,5 @@ use App\Entity\Parts\Storelocation; class StorelocationControllerTest extends AbstractAdminControllerTest { protected static string $base_path = '/en/store_location'; - protected static string $entity_class = Storelocation::class; + protected static string $entity_class = StorageLocation::class; } diff --git a/tests/Entity/Attachments/AttachmentTest.php b/tests/Entity/Attachments/AttachmentTest.php index 8d116398..321c71ba 100644 --- a/tests/Entity/Attachments/AttachmentTest.php +++ b/tests/Entity/Attachments/AttachmentTest.php @@ -33,7 +33,7 @@ use App\Entity\Attachments\GroupAttachment; use App\Entity\Attachments\ManufacturerAttachment; use App\Entity\Attachments\MeasurementUnitAttachment; use App\Entity\Attachments\PartAttachment; -use App\Entity\Attachments\StorelocationAttachment; +use App\Entity\Attachments\StorageLocationAttachment; use App\Entity\Attachments\SupplierAttachment; use App\Entity\Attachments\UserAttachment; use App\Entity\ProjectSystem\Project; @@ -42,7 +42,7 @@ use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; use App\Entity\UserSystem\Group; @@ -84,7 +84,7 @@ class AttachmentTest extends TestCase [ManufacturerAttachment::class, Manufacturer::class], [MeasurementUnitAttachment::class, MeasurementUnit::class], [PartAttachment::class, Part::class], - [StorelocationAttachment::class, Storelocation::class], + [StorageLocationAttachment::class, StorageLocation::class], [SupplierAttachment::class, Supplier::class], [UserAttachment::class, User::class], ]; diff --git a/tests/Entity/LogSystem/AbstractLogEntryTest.php b/tests/Entity/LogSystem/AbstractLogEntryTest.php index 134d80ce..3f223693 100644 --- a/tests/Entity/LogSystem/AbstractLogEntryTest.php +++ b/tests/Entity/LogSystem/AbstractLogEntryTest.php @@ -52,7 +52,7 @@ use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\Part; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\UserSystem\Group; use App\Entity\UserSystem\User; diff --git a/tests/Services/LabelSystem/Barcodes/BarcodeContentGeneratorTest.php b/tests/Services/LabelSystem/Barcodes/BarcodeContentGeneratorTest.php index f6597ffb..e14d9bc5 100644 --- a/tests/Services/LabelSystem/Barcodes/BarcodeContentGeneratorTest.php +++ b/tests/Services/LabelSystem/Barcodes/BarcodeContentGeneratorTest.php @@ -43,7 +43,7 @@ namespace App\Tests\Services\LabelSystem\Barcodes; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Services\LabelSystem\Barcodes\BarcodeContentGenerator; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; @@ -62,7 +62,7 @@ class BarcodeContentGeneratorTest extends KernelTestCase return [ ['P0000', Part::class], ['L0000', PartLot::class], - ['S0000', Storelocation::class], + ['S0000', StorageLocation::class], ]; } @@ -71,7 +71,7 @@ class BarcodeContentGeneratorTest extends KernelTestCase return [ ['/scan/part/0', Part::class], ['/scan/lot/0', PartLot::class], - ['/scan/location/0', Storelocation::class], + ['/scan/location/0', StorageLocation::class], ]; } diff --git a/tests/Services/LabelSystem/LabelGeneratorTest.php b/tests/Services/LabelSystem/LabelGeneratorTest.php index cefddeb5..ff347a93 100644 --- a/tests/Services/LabelSystem/LabelGeneratorTest.php +++ b/tests/Services/LabelSystem/LabelGeneratorTest.php @@ -46,7 +46,7 @@ use App\Entity\LabelSystem\LabelOptions; use App\Entity\LabelSystem\LabelSupportedElement; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Services\LabelSystem\LabelGenerator; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; @@ -68,7 +68,7 @@ class LabelGeneratorTest extends WebTestCase return [ [LabelSupportedElement::PART, Part::class], [LabelSupportedElement::PART_LOT, PartLot::class], - [LabelSupportedElement::STORELOCATION, Storelocation::class], + [LabelSupportedElement::STORELOCATION, StorageLocation::class], ]; } diff --git a/tests/Services/LabelSystem/PlaceholderProviders/PartLotProviderTest.php b/tests/Services/LabelSystem/PlaceholderProviders/PartLotProviderTest.php index 537c6223..bfcf4c43 100644 --- a/tests/Services/LabelSystem/PlaceholderProviders/PartLotProviderTest.php +++ b/tests/Services/LabelSystem/PlaceholderProviders/PartLotProviderTest.php @@ -43,7 +43,7 @@ namespace App\Tests\Services\LabelSystem\PlaceholderProviders; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\UserSystem\User; use App\Services\LabelSystem\PlaceholderProviders\PartLotProvider; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; @@ -68,9 +68,9 @@ class PartLotProviderTest extends WebTestCase $this->target->setExpirationDate(new \DateTime('1999-04-13')); $this->target->setInstockUnknown(true); - $location = new Storelocation(); + $location = new StorageLocation(); $location->setName('Location'); - $location->setParent((new Storelocation())->setName('Parent')); + $location->setParent((new StorageLocation())->setName('Parent')); $this->target->setStorageLocation($location); $part = new Part(); diff --git a/tests/Services/LabelSystem/SandboxedTwigProviderTest.php b/tests/Services/LabelSystem/SandboxedTwigProviderTest.php index b22a2ada..e62c54d2 100644 --- a/tests/Services/LabelSystem/SandboxedTwigProviderTest.php +++ b/tests/Services/LabelSystem/SandboxedTwigProviderTest.php @@ -46,7 +46,7 @@ use App\Entity\LabelSystem\LabelProcessMode; use App\Entity\LabelSystem\LabelSupportedElement; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Services\LabelSystem\SandboxedTwigProvider; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Twig\Sandbox\SecurityError; @@ -114,7 +114,7 @@ class SandboxedTwigProviderTest extends WebTestCase $str = $twig->render('lines', [ 'part' => new Part(), 'lot' => new PartLot(), - 'location' => new Storelocation(), + 'location' => new StorageLocation(), ]); $this->assertIsString($str); @@ -136,7 +136,7 @@ class SandboxedTwigProviderTest extends WebTestCase $str = $twig->render('lines', [ 'part' => new Part(), 'lot' => new PartLot(), - 'location' => new Storelocation(), + 'location' => new StorageLocation(), ]); $this->assertIsString($str); diff --git a/tests/Services/Parts/PartLotWithdrawAddHelperTest.php b/tests/Services/Parts/PartLotWithdrawAddHelperTest.php index b88e86e8..05a88f49 100644 --- a/tests/Services/Parts/PartLotWithdrawAddHelperTest.php +++ b/tests/Services/Parts/PartLotWithdrawAddHelperTest.php @@ -6,7 +6,7 @@ namespace App\Tests\Services\Parts; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Services\Parts\PartLotWithdrawAddHelper; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; @@ -29,10 +29,10 @@ class PartLotWithdrawAddHelperTest extends WebTestCase /** @var Part */ private Part $part; - /** @var Storelocation */ - private Storelocation $storageLocation; - /** @var Storelocation */ - private Storelocation $full_storageLocation; + /** @var StorageLocation */ + private StorageLocation $storageLocation; + /** @var StorageLocation */ + private StorageLocation $full_storageLocation; /** @var PartLot */ private PartLot $partLot1; @@ -59,8 +59,8 @@ class PartLotWithdrawAddHelperTest extends WebTestCase { $this->part = new Part(); - $this->storageLocation = new Storelocation(); - $this->full_storageLocation = new Storelocation(); + $this->storageLocation = new StorageLocation(); + $this->full_storageLocation = new StorageLocation(); $this->full_storageLocation->setIsFull(true); $this->partLot1 = new TestPartLot(); diff --git a/tests/Twig/EntityExtensionTest.php b/tests/Twig/EntityExtensionTest.php index 86e42542..3adb9ad2 100644 --- a/tests/Twig/EntityExtensionTest.php +++ b/tests/Twig/EntityExtensionTest.php @@ -30,7 +30,7 @@ use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; -use App\Entity\Parts\Storelocation; +use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; use App\Entity\UserSystem\Group; @@ -56,7 +56,7 @@ class EntityExtensionTest extends WebTestCase { $this->assertSame('part', $this->service->getEntityType(new Part())); $this->assertSame('footprint', $this->service->getEntityType(new Footprint())); - $this->assertSame('storelocation', $this->service->getEntityType(new Storelocation())); + $this->assertSame('storelocation', $this->service->getEntityType(new StorageLocation())); $this->assertSame('manufacturer', $this->service->getEntityType(new Manufacturer())); $this->assertSame('category', $this->service->getEntityType(new Category())); $this->assertSame('device', $this->service->getEntityType(new Project())); From 3738cb602528ed0ac8e6ccb4f508f2a1e2f4c52b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 4 Sep 2023 23:01:54 +0200 Subject: [PATCH 25/62] Fixed error in paramater discriminator map --- src/Entity/Parameters/AbstractParameter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Entity/Parameters/AbstractParameter.php b/src/Entity/Parameters/AbstractParameter.php index 513ab8dc..a3cef469 100644 --- a/src/Entity/Parameters/AbstractParameter.php +++ b/src/Entity/Parameters/AbstractParameter.php @@ -60,7 +60,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 => AttachmentTypeAttachment::class])] + 9 => SupplierParameter::class, 10 => AttachmentTypeParameter::class])] #[ORM\Table('parameters')] #[ORM\Index(name: 'parameter_name_idx', columns: ['name'])] #[ORM\Index(name: 'parameter_group_idx', columns: ['param_group'])] From 4e57750214ed83c8f6ee0213d1d3d69b072c5e69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 4 Sep 2023 23:05:30 +0200 Subject: [PATCH 26/62] Added endpoint for storage locations --- src/Entity/Parts/StorageLocation.php | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Entity/Parts/StorageLocation.php b/src/Entity/Parts/StorageLocation.php index 16915e9f..70ca1467 100644 --- a/src/Entity/Parts/StorageLocation.php +++ b/src/Entity/Parts/StorageLocation.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace App\Entity\Parts; use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Delete; use ApiPlatform\Metadata\Get; @@ -84,13 +85,19 @@ class StorageLocation extends AbstractPartsContainingDBElement #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] #[ORM\JoinColumn(name: 'parent_id')] + #[Groups(['location:read', 'location:write'])] + #[ApiProperty(readableLink: false, writableLink: false)] protected ?AbstractStructuralDBElement $parent = null; + #[Groups(['location:read', 'location:write'])] + protected string $comment = ''; + /** * @var MeasurementUnit|null The measurement unit, which parts can be stored in here */ #[ORM\ManyToOne(targetEntity: MeasurementUnit::class)] #[ORM\JoinColumn(name: 'storage_type_id')] + #[Groups(['location:read', 'location:write'])] protected ?MeasurementUnit $storage_type = null; /** @var Collection @@ -103,21 +110,21 @@ class StorageLocation extends AbstractPartsContainingDBElement /** * @var bool */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'location:read', 'location:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $is_full = false; /** * @var bool */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'location:read', 'location:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $only_single_part = false; /** * @var bool */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'location:read', 'location:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $limit_to_existing_parts = false; @@ -127,12 +134,14 @@ class StorageLocation extends AbstractPartsContainingDBElement #[Assert\Expression('this.getOwner() == null or this.getOwner().isAnonymousUser() === false', message: 'validator.part_lot.owner_must_not_be_anonymous')] #[ORM\ManyToOne(targetEntity: User::class)] #[ORM\JoinColumn(name: 'id_owner', onDelete: 'SET NULL')] + #[Groups(['location:read', 'location:write'])] protected ?User $owner = null; /** * @var bool If this is set to true, only parts lots, which are owned by the same user as the store location are allowed to be stored here. */ #[ORM\Column(type: Types::BOOLEAN, options: ['default' => false])] + #[Groups(['location:read', 'location:write'])] protected bool $part_owner_must_match = false; /** @@ -140,10 +149,12 @@ class StorageLocation extends AbstractPartsContainingDBElement */ #[Assert\Valid] #[ORM\OneToMany(targetEntity: StorageLocationAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[Groups(['location:read', 'location:write'])] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: StorageLocationAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] + #[Groups(['location:read', 'location:write'])] protected ?Attachment $master_picture_attachment = null; /******************************************************************************** From 64db3ed9651e2469335dc3d1da206a237e07c59b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 17 Sep 2023 11:24:12 +0200 Subject: [PATCH 27/62] Update dependencies --- composer.lock | 288 ++++++++++-------------------- yarn.lock | 479 ++++++++++++++++++-------------------------------- 2 files changed, 262 insertions(+), 505 deletions(-) diff --git a/composer.lock b/composer.lock index 0c605c92..89715776 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "api-platform/core", - "version": "v3.1.16", + "version": "v3.1.18", "source": { "type": "git", "url": "https://github.com/api-platform/core.git", - "reference": "9fa59e23f9000d6016aef00fad5b5cd72ac07919" + "reference": "198ca0f1f1c9f730a15e85762b04af43eb43424e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/core/zipball/9fa59e23f9000d6016aef00fad5b5cd72ac07919", - "reference": "9fa59e23f9000d6016aef00fad5b5cd72ac07919", + "url": "https://api.github.com/repos/api-platform/core/zipball/198ca0f1f1c9f730a15e85762b04af43eb43424e", + "reference": "198ca0f1f1c9f730a15e85762b04af43eb43424e", "shasum": "" }, "require": { @@ -31,6 +31,7 @@ "symfony/property-access": "^6.1", "symfony/property-info": "^6.1", "symfony/serializer": "^6.1", + "symfony/translation-contracts": "^3.3", "symfony/web-link": "^6.1", "willdurand/negotiation": "^3.0" }, @@ -43,11 +44,10 @@ "elasticsearch/elasticsearch": ">=8.0", "phpspec/prophecy": "<1.15", "phpunit/phpunit": "<9.5", - "symfony/service-contracts": "<3", "symfony/var-exporter": "<6.1.1" }, "require-dev": { - "behat/behat": "^3.1", + "behat/behat": "^3.11", "behat/mink": "^1.9", "doctrine/cache": "^1.11 || ^2.1", "doctrine/common": "^3.2.2", @@ -165,7 +165,7 @@ ], "support": { "issues": "https://github.com/api-platform/core/issues", - "source": "https://github.com/api-platform/core/tree/v3.1.16" + "source": "https://github.com/api-platform/core/tree/v3.1.18" }, "funding": [ { @@ -173,7 +173,7 @@ "type": "tidelift" } ], - "time": "2023-09-02T07:06:13+00:00" + "time": "2023-09-15T13:45:54+00:00" }, { "name": "beberlei/assert", @@ -2002,7 +2002,7 @@ "ext-json": "*", "ext-zip": "*", "mockery/mockery": "^1.3", - "phpunit/phpunit": "^7.5 || ^8 || ^9", + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10", "squizlabs/php_codesniffer": "^3.5", "symfony/process": "^4.4 || ^5.4 || ^6.2" }, @@ -2038,7 +2038,7 @@ "issues": "https://github.com/dompdf/dompdf/issues", "source": "https://github.com/dompdf/dompdf/tree/master" }, - "time": "2023-09-01T22:55:58+00:00" + "time": "2023-09-12T15:54:38+00:00" }, { "name": "egulias/email-validator", @@ -5412,16 +5412,16 @@ }, { "name": "php-translation/symfony-bundle", - "version": "0.14.0", + "version": "0.14.1", "source": { "type": "git", "url": "https://github.com/php-translation/symfony-bundle.git", - "reference": "1eb4d47c451eaae3efe2ca99b80b433cc4aeaf58" + "reference": "32db5ce0541cdaa2957f24b3c12a0ab1588770c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-translation/symfony-bundle/zipball/1eb4d47c451eaae3efe2ca99b80b433cc4aeaf58", - "reference": "1eb4d47c451eaae3efe2ca99b80b433cc4aeaf58", + "url": "https://api.github.com/repos/php-translation/symfony-bundle/zipball/32db5ce0541cdaa2957f24b3c12a0ab1588770c7", + "reference": "32db5ce0541cdaa2957f24b3c12a0ab1588770c7", "shasum": "" }, "require": { @@ -5483,9 +5483,9 @@ ], "support": { "issues": "https://github.com/php-translation/symfony-bundle/issues", - "source": "https://github.com/php-translation/symfony-bundle/tree/0.14.0" + "source": "https://github.com/php-translation/symfony-bundle/tree/0.14.1" }, - "time": "2023-07-11T17:58:52+00:00" + "time": "2023-09-12T09:35:28+00:00" }, { "name": "php-translation/symfony-storage", @@ -6444,16 +6444,16 @@ }, { "name": "s9e/text-formatter", - "version": "2.14.0", + "version": "2.14.1", "source": { "type": "git", "url": "https://github.com/s9e/TextFormatter.git", - "reference": "48a2f3a3fb18af8d78330204732a3369441c4060" + "reference": "19c0005a4c6513f73ed8d780959bcc5453b4fa37" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/48a2f3a3fb18af8d78330204732a3369441c4060", - "reference": "48a2f3a3fb18af8d78330204732a3369441c4060", + "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/19c0005a4c6513f73ed8d780959bcc5453b4fa37", + "reference": "19c0005a4c6513f73ed8d780959bcc5453b4fa37", "shasum": "" }, "require": { @@ -6480,7 +6480,7 @@ }, "type": "library", "extra": { - "version": "2.14.0" + "version": "2.14.1" }, "autoload": { "psr-4": { @@ -6512,9 +6512,9 @@ ], "support": { "issues": "https://github.com/s9e/TextFormatter/issues", - "source": "https://github.com/s9e/TextFormatter/tree/2.14.0" + "source": "https://github.com/s9e/TextFormatter/tree/2.14.1" }, - "time": "2023-06-08T07:19:50+00:00" + "time": "2023-09-14T09:16:53+00:00" }, { "name": "sabberworm/php-css-parser", @@ -11758,18 +11758,6 @@ }, { "name": "symfony/stimulus-bundle", -<<<<<<< HEAD - "version": "v2.11.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/stimulus-bundle.git", - "reference": "8c94fd88cbc9f4ee8177a2b04e69532a2e01145c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/stimulus-bundle/zipball/8c94fd88cbc9f4ee8177a2b04e69532a2e01145c", - "reference": "8c94fd88cbc9f4ee8177a2b04e69532a2e01145c", -======= "version": "v2.11.1", "source": { "type": "git", @@ -11780,7 +11768,6 @@ "type": "zip", "url": "https://api.github.com/repos/symfony/stimulus-bundle/zipball/e0e19de8df4d5b2bed57328ae69ef7904df660c7", "reference": "e0e19de8df4d5b2bed57328ae69ef7904df660c7", ->>>>>>> master "shasum": "" }, "require": { @@ -11819,11 +11806,7 @@ "symfony-ux" ], "support": { -<<<<<<< HEAD - "source": "https://github.com/symfony/stimulus-bundle/tree/v2.11.0" -======= "source": "https://github.com/symfony/stimulus-bundle/tree/v2.11.1" ->>>>>>> master }, "funding": [ { @@ -11839,11 +11822,7 @@ "type": "tidelift" } ], -<<<<<<< HEAD - "time": "2023-08-24T16:25:14+00:00" -======= "time": "2023-08-28T18:00:50+00:00" ->>>>>>> master }, { "name": "symfony/stopwatch", @@ -12435,11 +12414,7 @@ }, { "name": "symfony/ux-translator", -<<<<<<< HEAD - "version": "v2.11.0", -======= "version": "v2.11.1", ->>>>>>> master "source": { "type": "git", "url": "https://github.com/symfony/ux-translator.git", @@ -12495,11 +12470,7 @@ "symfony-ux" ], "support": { -<<<<<<< HEAD - "source": "https://github.com/symfony/ux-translator/tree/v2.11.0" -======= "source": "https://github.com/symfony/ux-translator/tree/v2.11.1" ->>>>>>> master }, "funding": [ { @@ -12519,20 +12490,16 @@ }, { "name": "symfony/ux-turbo", -<<<<<<< HEAD - "version": "v2.11.0", -======= - "version": "v2.11.1", ->>>>>>> master + "version": "v2.11.2", "source": { "type": "git", "url": "https://github.com/symfony/ux-turbo.git", - "reference": "561c1e4ed176fb99af9e0dcde449336fc04e6b0a" + "reference": "b245cb9d0682a1ddb287d496337420a40d0cb2a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ux-turbo/zipball/561c1e4ed176fb99af9e0dcde449336fc04e6b0a", - "reference": "561c1e4ed176fb99af9e0dcde449336fc04e6b0a", + "url": "https://api.github.com/repos/symfony/ux-turbo/zipball/b245cb9d0682a1ddb287d496337420a40d0cb2a6", + "reference": "b245cb9d0682a1ddb287d496337420a40d0cb2a6", "shasum": "" }, "require": { @@ -12599,11 +12566,7 @@ "turbo-stream" ], "support": { -<<<<<<< HEAD - "source": "https://github.com/symfony/ux-turbo/tree/v2.11.0" -======= - "source": "https://github.com/symfony/ux-turbo/tree/v2.11.1" ->>>>>>> master + "source": "https://github.com/symfony/ux-turbo/tree/v2.11.2" }, "funding": [ { @@ -12619,7 +12582,7 @@ "type": "tidelift" } ], - "time": "2023-08-24T16:25:14+00:00" + "time": "2023-09-06T19:09:16+00:00" }, { "name": "symfony/validator", @@ -13943,16 +13906,16 @@ }, { "name": "web-auth/metadata-service", - "version": "4.7.0", + "version": "4.7.1", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-metadata-service.git", - "reference": "d38751afc45b20e880ddb89b4e4c879b56b5a0be" + "reference": "33f4481be91c7a9a6809441400a664031e208f5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-metadata-service/zipball/d38751afc45b20e880ddb89b4e4c879b56b5a0be", - "reference": "d38751afc45b20e880ddb89b4e4c879b56b5a0be", + "url": "https://api.github.com/repos/web-auth/webauthn-metadata-service/zipball/33f4481be91c7a9a6809441400a664031e208f5c", + "reference": "33f4481be91c7a9a6809441400a664031e208f5c", "shasum": "" }, "require": { @@ -14008,7 +13971,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-metadata-service/tree/4.7.0" + "source": "https://github.com/web-auth/webauthn-metadata-service/tree/4.7.1" }, "funding": [ { @@ -14020,20 +13983,20 @@ "type": "patreon" } ], - "time": "2023-07-30T17:14:57+00:00" + "time": "2023-09-08T10:14:34+00:00" }, { "name": "web-auth/webauthn-lib", - "version": "4.7.0", + "version": "4.7.1", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-lib.git", - "reference": "760dda5f64be89bc535120b054b44f349e495a79" + "reference": "f4d2f3da7160ac1a634aca38e72b31629efdec87" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/760dda5f64be89bc535120b054b44f349e495a79", - "reference": "760dda5f64be89bc535120b054b44f349e495a79", + "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/f4d2f3da7160ac1a634aca38e72b31629efdec87", + "reference": "f4d2f3da7160ac1a634aca38e72b31629efdec87", "shasum": "" }, "require": { @@ -14096,7 +14059,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-lib/tree/4.7.0" + "source": "https://github.com/web-auth/webauthn-lib/tree/4.7.1" }, "funding": [ { @@ -14108,20 +14071,20 @@ "type": "patreon" } ], - "time": "2023-07-30T17:14:57+00:00" + "time": "2023-09-08T10:14:34+00:00" }, { "name": "web-auth/webauthn-symfony-bundle", - "version": "4.7.0", + "version": "4.7.1", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-symfony-bundle.git", - "reference": "11961539c90ed1bb87fc0ff31a7e3be5d0bfc1ac" + "reference": "15d2ecae6736304042759a58cec487d4507ee835" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-symfony-bundle/zipball/11961539c90ed1bb87fc0ff31a7e3be5d0bfc1ac", - "reference": "11961539c90ed1bb87fc0ff31a7e3be5d0bfc1ac", + "url": "https://api.github.com/repos/web-auth/webauthn-symfony-bundle/zipball/15d2ecae6736304042759a58cec487d4507ee835", + "reference": "15d2ecae6736304042759a58cec487d4507ee835", "shasum": "" }, "require": { @@ -14179,7 +14142,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-symfony-bundle/tree/4.7.0" + "source": "https://github.com/web-auth/webauthn-symfony-bundle/tree/4.7.1" }, "funding": [ { @@ -14191,7 +14154,7 @@ "type": "patreon" } ], - "time": "2023-07-30T17:14:57+00:00" + "time": "2023-09-08T10:14:34+00:00" }, { "name": "web-token/jwt-core", @@ -15430,29 +15393,16 @@ }, { "name": "phpstan/phpstan", -<<<<<<< HEAD - "version": "1.10.32", + "version": "1.10.34", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "c47e47d3ab03137c0e121e77c4d2cb58672f6d44" + "reference": "7f806b6f1403e6914c778140e2ba07c293cb4901" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c47e47d3ab03137c0e121e77c4d2cb58672f6d44", - "reference": "c47e47d3ab03137c0e121e77c4d2cb58672f6d44", -======= - "version": "1.10.33", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "03b1cf9f814ba0863c4e9affea49a4d1ed9a2ed1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/03b1cf9f814ba0863c4e9affea49a4d1ed9a2ed1", - "reference": "03b1cf9f814ba0863c4e9affea49a4d1ed9a2ed1", ->>>>>>> master + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/7f806b6f1403e6914c778140e2ba07c293cb4901", + "reference": "7f806b6f1403e6914c778140e2ba07c293cb4901", "shasum": "" }, "require": { @@ -15501,11 +15451,7 @@ "type": "tidelift" } ], -<<<<<<< HEAD - "time": "2023-08-24T21:54:50+00:00" -======= - "time": "2023-09-04T12:20:53+00:00" ->>>>>>> master + "time": "2023-09-13T09:49:47+00:00" }, { "name": "phpstan/phpstan-doctrine", @@ -15764,29 +15710,16 @@ }, { "name": "rector/rector", -<<<<<<< HEAD - "version": "0.18.1", + "version": "0.18.3", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "ee72ef542680a7f47ed8c6784f78b032c0d2f381" + "reference": "ba7988e3e028e68e07191d75b0d5473ac320c5e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/ee72ef542680a7f47ed8c6784f78b032c0d2f381", - "reference": "ee72ef542680a7f47ed8c6784f78b032c0d2f381", -======= - "version": "0.18.2", - "source": { - "type": "git", - "url": "https://github.com/rectorphp/rector.git", - "reference": "8606564b50ce70f99839d35c67f4536dc2ea090d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/8606564b50ce70f99839d35c67f4536dc2ea090d", - "reference": "8606564b50ce70f99839d35c67f4536dc2ea090d", ->>>>>>> master + "url": "https://api.github.com/repos/rectorphp/rector/zipball/ba7988e3e028e68e07191d75b0d5473ac320c5e7", + "reference": "ba7988e3e028e68e07191d75b0d5473ac320c5e7", "shasum": "" }, "require": { @@ -15821,11 +15754,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", -<<<<<<< HEAD - "source": "https://github.com/rectorphp/rector/tree/0.18.1" -======= - "source": "https://github.com/rectorphp/rector/tree/0.18.2" ->>>>>>> master + "source": "https://github.com/rectorphp/rector/tree/0.18.3" }, "funding": [ { @@ -15833,11 +15762,7 @@ "type": "github" } ], -<<<<<<< HEAD - "time": "2023-08-28T18:01:58+00:00" -======= - "time": "2023-09-06T08:50:38+00:00" ->>>>>>> master + "time": "2023-09-12T20:18:14+00:00" }, { "name": "roave/security-advisories", @@ -15845,21 +15770,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", -<<<<<<< HEAD - "reference": "9971379442eb64596a59f6c3b89ec66fac58c611" + "reference": "a4a221e6b171fe5eadf48e21ad8247304738bcd5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/9971379442eb64596a59f6c3b89ec66fac58c611", - "reference": "9971379442eb64596a59f6c3b89ec66fac58c611", -======= - "reference": "00de3fc12503b72bbd1dd43ee273213c811b184c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/00de3fc12503b72bbd1dd43ee273213c811b184c", - "reference": "00de3fc12503b72bbd1dd43ee273213c811b184c", ->>>>>>> master + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/a4a221e6b171fe5eadf48e21ad8247304738bcd5", + "reference": "a4a221e6b171fe5eadf48e21ad8247304738bcd5", "shasum": "" }, "conflict": { @@ -15926,6 +15842,7 @@ "cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4", "cartalyst/sentry": "<=2.1.6", "catfan/medoo": "<1.7.5", + "cecil/cecil": "<7.47.1", "centreon/centreon": "<22.10.0.0-beta1", "cesnet/simplesamlphp-module-proxystatistics": "<3.1", "chriskacerguis/codeigniter-restserver": "<=2.7.1", @@ -15952,6 +15869,7 @@ "czproject/git-php": "<4.0.3", "darylldoyle/safe-svg": "<1.9.10", "datadog/dd-trace": ">=0.30,<0.30.2", + "datatables/datatables": "<1.10.10", "david-garcia/phpwhois": "<=4.3.1", "dbrisinajumi/d2files": "<1", "dcat/laravel-admin": "<=2.1.3.0-beta", @@ -16028,7 +15946,7 @@ "friendsofsymfony/user-bundle": ">=1.2,<1.3.5", "friendsoftypo3/mediace": ">=7.6.2,<7.6.5", "friendsoftypo3/openid": ">=4.5,<4.5.31|>=4.7,<4.7.16|>=6,<6.0.11|>=6.1,<6.1.6", - "froala/wysiwyg-editor": "<3.2.7", + "froala/wysiwyg-editor": "<3.2.7|>=4.0.1,<=4.1.1", "froxlor/froxlor": "<2.1", "fuel/core": "<1.8.1", "funadmin/funadmin": "<=3.2|>=3.3.2,<=3.3.3", @@ -16183,7 +16101,7 @@ "open-web-analytics/open-web-analytics": "<1.7.4", "opencart/opencart": "<=3.0.3.7", "openid/php-openid": "<2.3", - "openmage/magento-lts": "<19.4.22|>=20,<20.0.19", + "openmage/magento-lts": "<=19.5|>=20,<=20.1", "opensource-workshop/connect-cms": "<1.7.2|>=2,<2.3.2", "orchid/platform": ">=9,<9.4.4|>=14.0.0.0-alpha4,<14.5", "oro/commerce": ">=4.1,<5.0.6", @@ -16227,7 +16145,7 @@ "pimcore/pimcore": "<10.6.8", "pixelfed/pixelfed": "<=0.11.4", "pocketmine/bedrock-protocol": "<8.0.2", - "pocketmine/pocketmine-mp": "<4.22.3|>=5,<5.2.1", + "pocketmine/pocketmine-mp": "<=4.23|>=5,<5.3.1", "pressbooks/pressbooks": "<5.18", "prestashop/autoupgrade": ">=4,<4.10.1", "prestashop/blockwishlist": ">=2,<2.1.1", @@ -16359,6 +16277,7 @@ "symfony/serializer": ">=2,<2.0.11|>=4.1,<4.4.35|>=5,<5.3.12", "symfony/symfony": "<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", "symfony/translation": ">=2,<2.0.17", + "symfony/ux-autocomplete": "<2.11.2", "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3", "symfony/var-exporter": ">=4.2,<4.2.12|>=4.3,<4.3.8", "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", @@ -16512,11 +16431,7 @@ "type": "tidelift" } ], -<<<<<<< HEAD - "time": "2023-08-31T19:04:14+00:00" -======= - "time": "2023-09-08T15:04:10+00:00" ->>>>>>> master + "time": "2023-09-15T19:04:11+00:00" }, { "name": "sebastian/diff", @@ -16859,52 +16774,50 @@ }, { "name": "symfony/maker-bundle", - "version": "v1.50.0", + "version": "v1.51.0", "source": { "type": "git", "url": "https://github.com/symfony/maker-bundle.git", - "reference": "a1733f849b999460c308e66f6392fb09b621fa86" + "reference": "d5684e5628d545a67ba833adcd216e7029f3e506" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/a1733f849b999460c308e66f6392fb09b621fa86", - "reference": "a1733f849b999460c308e66f6392fb09b621fa86", + "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/d5684e5628d545a67ba833adcd216e7029f3e506", + "reference": "d5684e5628d545a67ba833adcd216e7029f3e506", "shasum": "" }, "require": { "doctrine/inflector": "^2.0", "nikic/php-parser": "^4.11", - "php": ">=8.0", - "symfony/config": "^5.4.7|^6.0", - "symfony/console": "^5.4.7|^6.0", - "symfony/dependency-injection": "^5.4.7|^6.0", + "php": ">=8.1", + "symfony/config": "^6.3|^7.0", + "symfony/console": "^6.3|^7.0", + "symfony/dependency-injection": "^6.3|^7.0", "symfony/deprecation-contracts": "^2.2|^3", - "symfony/filesystem": "^5.4.7|^6.0", - "symfony/finder": "^5.4.3|^6.0", - "symfony/framework-bundle": "^5.4.7|^6.0", - "symfony/http-kernel": "^5.4.7|^6.0", - "symfony/process": "^5.4.7|^6.0" + "symfony/filesystem": "^6.3|^7.0", + "symfony/finder": "^6.3|^7.0", + "symfony/framework-bundle": "^6.3|^7.0", + "symfony/http-kernel": "^6.3|^7.0", + "symfony/process": "^6.3|^7.0" }, "conflict": { "doctrine/doctrine-bundle": "<2.4", - "doctrine/orm": "<2.10", - "symfony/doctrine-bridge": "<5.4" + "doctrine/orm": "<2.10" }, "require-dev": { "composer/semver": "^3.0", - "doctrine/doctrine-bundle": "^2.4", + "doctrine/doctrine-bundle": "^2.5.0", "doctrine/orm": "^2.10.0", - "symfony/http-client": "^5.4.7|^6.0", - "symfony/phpunit-bridge": "^5.4.17|^6.0", - "symfony/polyfill-php80": "^1.16.0", - "symfony/security-core": "^5.4.7|^6.0", - "symfony/yaml": "^5.4.3|^6.0", + "symfony/http-client": "^6.3|^7.0", + "symfony/phpunit-bridge": "^6.3|^7.0", + "symfony/security-core": "^6.3|^7.0", + "symfony/yaml": "^6.3|^7.0", "twig/twig": "^2.0|^3.0" }, "type": "symfony-bundle", "extra": { "branch-alias": { - "dev-main": "1.0-dev" + "dev-main": "1.x-dev" } }, "autoload": { @@ -16933,7 +16846,7 @@ ], "support": { "issues": "https://github.com/symfony/maker-bundle/issues", - "source": "https://github.com/symfony/maker-bundle/tree/v1.50.0" + "source": "https://github.com/symfony/maker-bundle/tree/v1.51.0" }, "funding": [ { @@ -16949,7 +16862,7 @@ "type": "tidelift" } ], - "time": "2023-07-10T18:21:57+00:00" + "time": "2023-09-12T18:09:19+00:00" }, { "name": "symfony/phpunit-bridge", @@ -17115,18 +17028,6 @@ }, { "name": "symplify/easy-coding-standard", -<<<<<<< HEAD - "version": "12.0.7", - "source": { - "type": "git", - "url": "https://github.com/easy-coding-standard/easy-coding-standard.git", - "reference": "79b5679f0dd758311770543969087b8dc46429ee" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/79b5679f0dd758311770543969087b8dc46429ee", - "reference": "79b5679f0dd758311770543969087b8dc46429ee", -======= "version": "12.0.8", "source": { "type": "git", @@ -17137,7 +17038,6 @@ "type": "zip", "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/99d87d188acc712dd6655ee946569f823cfeff69", "reference": "99d87d188acc712dd6655ee946569f823cfeff69", ->>>>>>> master "shasum": "" }, "require": { @@ -17170,11 +17070,7 @@ ], "support": { "issues": "https://github.com/easy-coding-standard/easy-coding-standard/issues", -<<<<<<< HEAD - "source": "https://github.com/easy-coding-standard/easy-coding-standard/tree/12.0.7" -======= "source": "https://github.com/easy-coding-standard/easy-coding-standard/tree/12.0.8" ->>>>>>> master }, "funding": [ { @@ -17186,11 +17082,7 @@ "type": "github" } ], -<<<<<<< HEAD - "time": "2023-08-24T21:43:33+00:00" -======= "time": "2023-09-08T10:17:14+00:00" ->>>>>>> master }, { "name": "vimeo/psalm", diff --git a/yarn.lock b/yarn.lock index e32c05a8..231b5f35 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,11 +10,7 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -<<<<<<< HEAD -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.22.10", "@babel/code-frame@^7.22.5": -======= "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.22.13": ->>>>>>> master version "7.22.13" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== @@ -22,43 +18,26 @@ "@babel/highlight" "^7.22.13" chalk "^2.4.2" -"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730" - integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ== +"@babel/compat-data@^7.22.20", "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.20.tgz#8df6e96661209623f1975d66c35ffca66f3306d0" + integrity sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw== "@babel/core@^7.19.6": -<<<<<<< HEAD - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.11.tgz#8033acaa2aa24c3f814edaaa057f3ce0ba559c24" - integrity sha512-lh7RJrtPdhibbxndr6/xx0w8+CVlY5FJZiaSz908Fpy+G0xkBFTvwLcKJFF4PJxVfGhVWNebikpWGnOoC71juQ== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.22.10" - "@babel/generator" "^7.22.10" - "@babel/helper-compilation-targets" "^7.22.10" - "@babel/helper-module-transforms" "^7.22.9" - "@babel/helpers" "^7.22.11" - "@babel/parser" "^7.22.11" - "@babel/template" "^7.22.5" - "@babel/traverse" "^7.22.11" - "@babel/types" "^7.22.11" -======= - version "7.22.17" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.17.tgz#2f9b0b395985967203514b24ee50f9fd0639c866" - integrity sha512-2EENLmhpwplDux5PSsZnSbnSkB3tZ6QTksgO25xwEL7pIDcNOMhF5v/s6RzwjMZzZzw9Ofc30gHv5ChCC8pifQ== + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.20.tgz#e3d0eed84c049e2a2ae0a64d27b6a37edec385b7" + integrity sha512-Y6jd1ahLubuYweD/zJH+vvOY141v4f9igNQAQ+MBgq9JlHS2iTsZKn1aMsb3vGccZsXI16VzTBw52Xx0DWmtnA== dependencies: "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.22.13" "@babel/generator" "^7.22.15" "@babel/helper-compilation-targets" "^7.22.15" - "@babel/helper-module-transforms" "^7.22.17" + "@babel/helper-module-transforms" "^7.22.20" "@babel/helpers" "^7.22.15" "@babel/parser" "^7.22.16" "@babel/template" "^7.22.15" - "@babel/traverse" "^7.22.17" - "@babel/types" "^7.22.17" ->>>>>>> master + "@babel/traverse" "^7.22.20" + "@babel/types" "^7.22.19" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -101,15 +80,9 @@ semver "^6.3.1" "@babel/helper-create-class-features-plugin@^7.22.11", "@babel/helper-create-class-features-plugin@^7.22.5": -<<<<<<< HEAD - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.11.tgz#4078686740459eeb4af3494a273ac09148dfb213" - integrity sha512-y1grdYL4WzmUDBRGK0pDbIoFd7UZKoDurDzWEoNMYoj1EL+foGRQNyPWDcC+YyegN5y1DUsFFmzjGijB3nSVAQ== -======= version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz#97a61b385e57fe458496fad19f8e63b63c867de4" integrity sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg== ->>>>>>> master dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" "@babel/helper-environment-visitor" "^7.22.5" @@ -141,10 +114,10 @@ lodash.debounce "^4.0.8" resolve "^1.14.2" -"@babel/helper-environment-visitor@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98" - integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q== +"@babel/helper-environment-visitor@^7.22.20", "@babel/helper-environment-visitor@^7.22.5": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== "@babel/helper-function-name@^7.22.5": version "7.22.5" @@ -161,7 +134,7 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-member-expression-to-functions@^7.22.15", "@babel/helper-member-expression-to-functions@^7.22.5": +"@babel/helper-member-expression-to-functions@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.15.tgz#b95a144896f6d491ca7863576f820f3628818621" integrity sha512-qLNsZbgrNh0fDQBCPocSL8guki1hcPvltGDv/NxvUoABwFq7GkKSu1nRXeJkVZc+wJvne2E0RKQz+2SQrz6eAA== @@ -175,16 +148,16 @@ dependencies: "@babel/types" "^7.22.15" -"@babel/helper-module-transforms@^7.22.15", "@babel/helper-module-transforms@^7.22.17", "@babel/helper-module-transforms@^7.22.5", "@babel/helper-module-transforms@^7.22.9": - version "7.22.17" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.17.tgz#7edf129097a51ccc12443adbc6320e90eab76693" - integrity sha512-XouDDhQESrLHTpnBtCKExJdyY4gJCdrvH2Pyv8r8kovX2U8G0dRUOT45T9XlbLtuu9CLXP15eusnkprhoPV5iQ== +"@babel/helper-module-transforms@^7.22.15", "@babel/helper-module-transforms@^7.22.20", "@babel/helper-module-transforms@^7.22.5", "@babel/helper-module-transforms@^7.22.9": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.20.tgz#da9edc14794babbe7386df438f3768067132f59e" + integrity sha512-dLT7JVWIUUxKOs1UnJUBR3S70YK+pKX6AbJgB2vMIvEkZkrfJDbYDJesnPshtKV4LhDOR3Oc5YULeDizRek+5A== dependencies: - "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" "@babel/helper-module-imports" "^7.22.15" "@babel/helper-simple-access" "^7.22.5" "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/helper-validator-identifier" "^7.22.15" + "@babel/helper-validator-identifier" "^7.22.20" "@babel/helper-optimise-call-expression@^7.22.5": version "7.22.5" @@ -199,21 +172,21 @@ integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== "@babel/helper-remap-async-to-generator@^7.22.5", "@babel/helper-remap-async-to-generator@^7.22.9": - version "7.22.17" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.17.tgz#dabaa50622b3b4670bd6546fc8db23eb12d89da0" - integrity sha512-bxH77R5gjH3Nkde6/LuncQoLaP16THYPscurp1S8z7S9ZgezCyV3G8Hc+TZiCmY8pz4fp8CvKSgtJMW0FkLAxA== + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz#7b68e1cb4fa964d2996fd063723fb48eca8498e0" + integrity sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-wrap-function" "^7.22.17" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-wrap-function" "^7.22.20" "@babel/helper-replace-supers@^7.22.5", "@babel/helper-replace-supers@^7.22.9": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz#cbdc27d6d8d18cd22c81ae4293765a5d9afd0779" - integrity sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg== + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz#e37d367123ca98fe455a9887734ed2e16eb7a793" + integrity sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw== dependencies: - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-member-expression-to-functions" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-member-expression-to-functions" "^7.22.15" "@babel/helper-optimise-call-expression" "^7.22.5" "@babel/helper-simple-access@^7.22.5": @@ -242,35 +215,25 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== -"@babel/helper-validator-identifier@^7.22.15", "@babel/helper-validator-identifier@^7.22.5": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz#601fa28e4cc06786c18912dca138cec73b882044" - integrity sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ== +"@babel/helper-validator-identifier@^7.22.19", "@babel/helper-validator-identifier@^7.22.20", "@babel/helper-validator-identifier@^7.22.5": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== "@babel/helper-validator-option@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040" integrity sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA== -"@babel/helper-wrap-function@^7.22.17": - version "7.22.17" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.17.tgz#222ac3ff9cc8f9b617cc1e5db75c0b538e722801" - integrity sha512-nAhoheCMlrqU41tAojw9GpVEKDlTS8r3lzFmF0lP52LwblCPbuFSO7nGIZoIcoU5NIm1ABrna0cJExE4Ay6l2Q== +"@babel/helper-wrap-function@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz#15352b0b9bfb10fc9c76f79f6342c00e3411a569" + integrity sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw== dependencies: "@babel/helper-function-name" "^7.22.5" "@babel/template" "^7.22.15" - "@babel/types" "^7.22.17" + "@babel/types" "^7.22.19" -<<<<<<< HEAD -"@babel/helpers@^7.22.11": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.11.tgz#b02f5d5f2d7abc21ab59eeed80de410ba70b056a" - integrity sha512-vyOXC8PBWaGc5h7GMsNx68OH33cypkEDJCHvYVVgVbbxJDROYVtexSk0gK5iCF1xNjRIN2s8ai7hwkWDq5szWg== - dependencies: - "@babel/template" "^7.22.5" - "@babel/traverse" "^7.22.11" - "@babel/types" "^7.22.11" -======= "@babel/helpers@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.15.tgz#f09c3df31e86e3ea0b7ff7556d85cdebd47ea6f1" @@ -279,28 +242,20 @@ "@babel/template" "^7.22.15" "@babel/traverse" "^7.22.15" "@babel/types" "^7.22.15" ->>>>>>> master "@babel/highlight@^7.22.13": - version "7.22.13" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.13.tgz#9cda839e5d3be9ca9e8c26b6dd69e7548f0cbf16" - integrity sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ== + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" + integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== dependencies: - "@babel/helper-validator-identifier" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.20" chalk "^2.4.2" js-tokens "^4.0.0" -<<<<<<< HEAD -"@babel/parser@^7.18.9", "@babel/parser@^7.22.11", "@babel/parser@^7.22.5": - version "7.22.14" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.14.tgz#c7de58e8de106e88efca42ce17f0033209dfd245" - integrity sha512-1KucTHgOvaw/LzCVrEOAyXkr9rQlp0A1HiHRYnSUE9dmb8PvPW7o5sscg+5169r54n3vGlbx6GevTE/Iw/P3AQ== -======= "@babel/parser@^7.18.9", "@babel/parser@^7.22.15", "@babel/parser@^7.22.16": version "7.22.16" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.16.tgz#180aead7f247305cce6551bea2720934e2fa2c95" integrity sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA== ->>>>>>> master "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.15": version "7.22.15" @@ -457,17 +412,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -<<<<<<< HEAD -"@babel/plugin-transform-async-generator-functions@^7.22.11": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.11.tgz#dbe3b1ff5a52e2e5edc4b19a60d325a675ed2649" - integrity sha512-0pAlmeRJn6wU84zzZsEOx1JV1Jf8fqO9ok7wofIJwUnplYo247dcd24P+cMJht7ts9xkzdtB0EPHmOb7F+KzXw== -======= "@babel/plugin-transform-async-generator-functions@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.15.tgz#3b153af4a6b779f340d5b80d3f634f55820aefa3" integrity sha512-jBm1Es25Y+tVoTi5rfd5t1KLmL8ogLKpXszboWOTTtGFGz2RKnQe2yn7HbZ+kb/B8N0FVSGQo874NSlOU1T4+w== ->>>>>>> master dependencies: "@babel/helper-environment-visitor" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" @@ -637,21 +585,12 @@ "@babel/helper-module-transforms" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -<<<<<<< HEAD -"@babel/plugin-transform-modules-commonjs@^7.22.11": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.11.tgz#d7991d3abad199c03b68ee66a64f216c47ffdfae" - integrity sha512-o2+bg7GDS60cJMgz9jWqRUsWkMzLCxp+jFDeDUT5sjRlAxcJWZ2ylNdI7QQ2+CH5hWu7OnN+Cv3htt7AkSf96g== - dependencies: - "@babel/helper-module-transforms" "^7.22.9" -======= "@babel/plugin-transform-modules-commonjs@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.15.tgz#b11810117ed4ee7691b29bd29fd9f3f98276034f" integrity sha512-jWL4eh90w0HQOTKP2MoXXUpVxilxsB2Vl4ji69rSjS3EcZ/v4sBmn+A3NpepuJzBhOaEBbR7udonlHHn5DWidg== dependencies: "@babel/helper-module-transforms" "^7.22.15" ->>>>>>> master "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-simple-access" "^7.22.5" @@ -704,15 +643,6 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-numeric-separator" "^7.10.4" -<<<<<<< HEAD -"@babel/plugin-transform-object-rest-spread@^7.22.11": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.11.tgz#dbbb06ce783cd994a8f430d8cefa553e9b42ca62" - integrity sha512-nX8cPFa6+UmbepISvlf5jhQyaC7ASs/7UxHmMkuJ/k5xSHvDPPaibMo+v3TXwU/Pjqhep/nFNpd3zn4YR59pnw== - dependencies: - "@babel/compat-data" "^7.22.9" - "@babel/helper-compilation-targets" "^7.22.10" -======= "@babel/plugin-transform-object-rest-spread@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.15.tgz#21a95db166be59b91cde48775310c0df6e1da56f" @@ -720,7 +650,6 @@ dependencies: "@babel/compat-data" "^7.22.9" "@babel/helper-compilation-targets" "^7.22.15" ->>>>>>> master "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-transform-parameters" "^7.22.15" @@ -741,17 +670,10 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -<<<<<<< HEAD -"@babel/plugin-transform-optional-chaining@^7.22.12", "@babel/plugin-transform-optional-chaining@^7.22.5": - version "7.22.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.12.tgz#d7ebf6a88cd2f4d307b0e000ab630acd8124b333" - integrity sha512-7XXCVqZtyFWqjDsYDY4T45w4mlx1rf7aOgkc/Ww76xkgBiOlmjPkx36PBLHa1k1rwWvVgYMPsbuVnIamx2ZQJw== -======= "@babel/plugin-transform-optional-chaining@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.15.tgz#d7a5996c2f7ca4ad2ad16dbb74444e5c4385b1ba" integrity sha512-ngQ2tBhq5vvSJw2Q2Z9i7ealNkpDMU0rGWnHPKqRZO0tzZ5tlaoz4hDvhXioOoaE0X2vfNss1djwg0DXlfu30A== ->>>>>>> master dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" @@ -872,17 +794,11 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/preset-env@^7.19.4": -<<<<<<< HEAD - version "7.22.14" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.14.tgz#1cbb468d899f64fa71c53446f13b7ff8c0005cc1" - integrity sha512-daodMIoVo+ol/g+//c/AH+szBkFj4STQUikvBijRGL72Ph+w+AMTSh55DUETe8KJlPlDT1k/mp7NBfOuiWmoig== -======= - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.15.tgz#142716f8e00bc030dae5b2ac6a46fbd8b3e18ff8" - integrity sha512-tZFHr54GBkHk6hQuVA8w4Fmq+MSPsfvMG0vPnOYyTnJpyfMqybL8/MbNCPRT9zc2KBO2pe4tq15g6Uno4Jpoag== ->>>>>>> master + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.20.tgz#de9e9b57e1127ce0a2f580831717f7fb677ceedb" + integrity sha512-11MY04gGC4kSzlPHRfvVkNAZhUxOvm7DCJ37hPDnUENwe06npjIRAfInEMTGSb4LZK5ZgDFkv5hw0lGebHeTyg== dependencies: - "@babel/compat-data" "^7.22.9" + "@babel/compat-data" "^7.22.20" "@babel/helper-compilation-targets" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-validator-option" "^7.22.15" @@ -908,21 +824,13 @@ "@babel/plugin-syntax-top-level-await" "^7.14.5" "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" "@babel/plugin-transform-arrow-functions" "^7.22.5" -<<<<<<< HEAD - "@babel/plugin-transform-async-generator-functions" "^7.22.11" -======= "@babel/plugin-transform-async-generator-functions" "^7.22.15" ->>>>>>> master "@babel/plugin-transform-async-to-generator" "^7.22.5" "@babel/plugin-transform-block-scoped-functions" "^7.22.5" "@babel/plugin-transform-block-scoping" "^7.22.15" "@babel/plugin-transform-class-properties" "^7.22.5" "@babel/plugin-transform-class-static-block" "^7.22.11" -<<<<<<< HEAD - "@babel/plugin-transform-classes" "^7.22.6" -======= "@babel/plugin-transform-classes" "^7.22.15" ->>>>>>> master "@babel/plugin-transform-computed-properties" "^7.22.5" "@babel/plugin-transform-destructuring" "^7.22.15" "@babel/plugin-transform-dotall-regex" "^7.22.5" @@ -930,41 +838,25 @@ "@babel/plugin-transform-dynamic-import" "^7.22.11" "@babel/plugin-transform-exponentiation-operator" "^7.22.5" "@babel/plugin-transform-export-namespace-from" "^7.22.11" -<<<<<<< HEAD - "@babel/plugin-transform-for-of" "^7.22.5" -======= "@babel/plugin-transform-for-of" "^7.22.15" ->>>>>>> master "@babel/plugin-transform-function-name" "^7.22.5" "@babel/plugin-transform-json-strings" "^7.22.11" "@babel/plugin-transform-literals" "^7.22.5" "@babel/plugin-transform-logical-assignment-operators" "^7.22.11" "@babel/plugin-transform-member-expression-literals" "^7.22.5" "@babel/plugin-transform-modules-amd" "^7.22.5" -<<<<<<< HEAD - "@babel/plugin-transform-modules-commonjs" "^7.22.11" -======= "@babel/plugin-transform-modules-commonjs" "^7.22.15" ->>>>>>> master "@babel/plugin-transform-modules-systemjs" "^7.22.11" "@babel/plugin-transform-modules-umd" "^7.22.5" "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.5" "@babel/plugin-transform-new-target" "^7.22.5" "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.11" "@babel/plugin-transform-numeric-separator" "^7.22.11" -<<<<<<< HEAD - "@babel/plugin-transform-object-rest-spread" "^7.22.11" - "@babel/plugin-transform-object-super" "^7.22.5" - "@babel/plugin-transform-optional-catch-binding" "^7.22.11" - "@babel/plugin-transform-optional-chaining" "^7.22.12" - "@babel/plugin-transform-parameters" "^7.22.5" -======= "@babel/plugin-transform-object-rest-spread" "^7.22.15" "@babel/plugin-transform-object-super" "^7.22.5" "@babel/plugin-transform-optional-catch-binding" "^7.22.11" "@babel/plugin-transform-optional-chaining" "^7.22.15" "@babel/plugin-transform-parameters" "^7.22.15" ->>>>>>> master "@babel/plugin-transform-private-methods" "^7.22.5" "@babel/plugin-transform-private-property-in-object" "^7.22.11" "@babel/plugin-transform-property-literals" "^7.22.5" @@ -980,11 +872,7 @@ "@babel/plugin-transform-unicode-regex" "^7.22.5" "@babel/plugin-transform-unicode-sets-regex" "^7.22.5" "@babel/preset-modules" "0.1.6-no-external-plugins" -<<<<<<< HEAD - "@babel/types" "^7.22.11" -======= - "@babel/types" "^7.22.15" ->>>>>>> master + "@babel/types" "^7.22.19" babel-plugin-polyfill-corejs2 "^0.4.5" babel-plugin-polyfill-corejs3 "^0.8.3" babel-plugin-polyfill-regenerator "^0.5.2" @@ -1006,15 +894,9 @@ integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== "@babel/runtime@^7.8.4": -<<<<<<< HEAD - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.11.tgz#7a9ba3bbe406ad6f9e8dd4da2ece453eb23a77a4" - integrity sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA== -======= version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.15.tgz#38f46494ccf6cf020bd4eed7124b425e83e523b8" integrity sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA== ->>>>>>> master dependencies: regenerator-runtime "^0.14.0" @@ -1027,48 +909,29 @@ "@babel/parser" "^7.22.15" "@babel/types" "^7.22.15" -<<<<<<< HEAD -"@babel/traverse@^7.18.9", "@babel/traverse@^7.22.11": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.11.tgz#71ebb3af7a05ff97280b83f05f8865ac94b2027c" - integrity sha512-mzAenteTfomcB7mfPtyi+4oe5BZ6MXxWcn4CX+h4IRJ+OOGXBrWU6jDQavkQI9Vuc5P+donFabBfFCcmWka9lQ== -======= -"@babel/traverse@^7.18.9", "@babel/traverse@^7.22.15", "@babel/traverse@^7.22.17": - version "7.22.17" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.17.tgz#b23c203ab3707e3be816043081b4a994fcacec44" - integrity sha512-xK4Uwm0JnAMvxYZxOVecss85WxTEIbTa7bnGyf/+EgCL5Zt3U7htUpEOWv9detPlamGKuRzCqw74xVglDWpPdg== ->>>>>>> master +"@babel/traverse@^7.18.9", "@babel/traverse@^7.22.15", "@babel/traverse@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.20.tgz#db572d9cb5c79e02d83e5618b82f6991c07584c9" + integrity sha512-eU260mPZbU7mZ0N+X10pxXhQFMGTeLb9eFS0mxehS8HZp9o1uSnFeWQuG1UPrlxgA7QoUzFhOnilHDp0AXCyHw== dependencies: "@babel/code-frame" "^7.22.13" "@babel/generator" "^7.22.15" - "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" "@babel/helper-function-name" "^7.22.5" "@babel/helper-hoist-variables" "^7.22.5" "@babel/helper-split-export-declaration" "^7.22.6" -<<<<<<< HEAD - "@babel/parser" "^7.22.11" - "@babel/types" "^7.22.11" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/types@^7.22.10", "@babel/types@^7.22.11", "@babel/types@^7.22.5", "@babel/types@^7.4.4": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.11.tgz#0e65a6a1d4d9cbaa892b2213f6159485fe632ea2" - integrity sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg== -======= "@babel/parser" "^7.22.16" - "@babel/types" "^7.22.17" + "@babel/types" "^7.22.19" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.22.15", "@babel/types@^7.22.17", "@babel/types@^7.22.5", "@babel/types@^7.4.4": - version "7.22.17" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.17.tgz#f753352c4610ffddf9c8bc6823f9ff03e2303eee" - integrity sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg== ->>>>>>> master +"@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.4.4": + version "7.22.19" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.19.tgz#7425343253556916e440e662bb221a93ddb75684" + integrity sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg== dependencies: "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.15" + "@babel/helper-validator-identifier" "^7.22.19" to-fast-properties "^2.0.0" "@ckeditor/ckeditor5-alignment@^39.0.1": @@ -1599,12 +1462,12 @@ resolved "https://registry.yarnpkg.com/@foliojs-fork/restructure/-/restructure-2.0.2.tgz#73759aba2aff1da87b7c4554e6839c70d43c92b4" integrity sha512-59SgoZ3EXbkfSX7b63tsou/SDGzwUEK6MuB5sKqgVK1/XE0fxmpsOb9DQI8LXW3KfGnAjImCGhhEb7uPPAUVNA== -"@formatjs/ecma402-abstract@1.17.1": - version "1.17.1" - resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.17.1.tgz#6ac7d6a1d1c9c8eff76ab6ed949f2a5cbe424030" - integrity sha512-N2sjSUrmsEoynG8Q61pkrKlJ9PxcUGxJke1x3301aGyprGgl58wHWhgGUnzTfS4OHNNNQDxzjcXVp1t5fGW6yQ== +"@formatjs/ecma402-abstract@1.17.2": + version "1.17.2" + resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.17.2.tgz#d197c6e26b9fd96ff7ba3b3a0cc2f25f1f2dcac3" + integrity sha512-k2mTh0m+IV1HRdU0xXM617tSQTi53tVR2muvYOsBeYcUgEAyxV1FOC7Qj279th3fBVQ+Dj6muvNJZcHSPNdbKg== dependencies: - "@formatjs/intl-localematcher" "0.4.1" + "@formatjs/intl-localematcher" "0.4.2" tslib "^2.4.0" "@formatjs/fast-memoize@2.2.0": @@ -1614,27 +1477,27 @@ dependencies: tslib "^2.4.0" -"@formatjs/icu-messageformat-parser@2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.6.1.tgz#ca497d5a2bff641dc0978bd9b64d1d02597980cb" - integrity sha512-dTDNupwdovxT1xDXC96zzPUua/XrxTQTOulJZSvaJP0pt3rr/cGR/tq4d7BnxY9oqPZpc4fjWBmrRlhcUyBSiw== +"@formatjs/icu-messageformat-parser@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.6.2.tgz#9bbb29099416e4ce2c7df50029c48985d4f901b3" + integrity sha512-nF/Iww7sc5h+1MBCDRm68qpHTCG4xvGzYs/x9HFcDETSGScaJ1Fcadk5U/NXjXeCtzD+DhN4BAwKFVclHfKMdA== dependencies: - "@formatjs/ecma402-abstract" "1.17.1" - "@formatjs/icu-skeleton-parser" "1.6.1" + "@formatjs/ecma402-abstract" "1.17.2" + "@formatjs/icu-skeleton-parser" "1.6.2" tslib "^2.4.0" -"@formatjs/icu-skeleton-parser@1.6.1": - version "1.6.1" - resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.6.1.tgz#3647f41b82e362c08bb80bd9b653c7eb6ff31118" - integrity sha512-/LQ6ovxYd8FQjVLmbV+WmuFy86o+JTc0cIQuWixuLuUMfRRif8eUQw3vPK5hx7C/g1UVmKAaOcYRTEsvyEGz9g== +"@formatjs/icu-skeleton-parser@1.6.2": + version "1.6.2" + resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.6.2.tgz#00303034dc08583973c8aa67b96534c49c0bad8d" + integrity sha512-VtB9Slo4ZL6QgtDFJ8Injvscf0xiDd4bIV93SOJTBjUF4xe2nAWOoSjLEtqIG+hlIs1sNrVKAaFo3nuTI4r5ZA== dependencies: - "@formatjs/ecma402-abstract" "1.17.1" + "@formatjs/ecma402-abstract" "1.17.2" tslib "^2.4.0" -"@formatjs/intl-localematcher@0.4.1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.4.1.tgz#af63e2c065731a33f6fed36dc85058009a7f8062" - integrity sha512-Fs4MhhHlLC0RrspX2u2KP7zlwL9eHrBZsOBxaPOeqrCZYLaOUK4cYXQ1ErpAB0HnGV/GUXNa5smzV/7jCuRzxg== +"@formatjs/intl-localematcher@0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.4.2.tgz#7e6e596dbaf2f0c5a7c22da5a01d5c55f4c37e9a" + integrity sha512-BGdtJFmaNJy5An/Zan4OId/yR9Ih1OojFjcduX/xOvq798OgWSyDtd6Qd5jqJXwJs1ipe4Fxu9+cshic5Ox2tA== dependencies: tslib "^2.4.0" @@ -1860,17 +1723,17 @@ integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== "@types/body-parser@*": - version "1.19.2" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" - integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + version "1.19.3" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.3.tgz#fb558014374f7d9e56c8f34bab2042a3a07d25cd" + integrity sha512-oyl4jvAfTGX9Bt6Or4H9ni1Z447/tQuxnZsytsCaExKlmJiU8sFgnIBRzJUpKwB5eWn9HuBYlUlVA74q/yN0eQ== dependencies: "@types/connect" "*" "@types/node" "*" "@types/bonjour@^3.5.9": - version "3.5.10" - resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.10.tgz#0f6aadfe00ea414edc86f5d106357cda9701e275" - integrity sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw== + version "3.5.11" + resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.11.tgz#fbaa46a1529ea5c5e46cde36e4be6a880db55b84" + integrity sha512-isGhjmBtLIxdHBDl2xGwUzEM8AOyOvWsADWq7rqirdi/ZQoHnLWErHvsThcEzTX8juDRiZtzp2Qkv5bgNh6mAg== dependencies: "@types/node" "*" @@ -1939,14 +1802,14 @@ "@types/node" "*" "@types/http-errors@*": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.1.tgz#20172f9578b225f6c7da63446f56d4ce108d5a65" - integrity sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ== + version "2.0.2" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.2.tgz#a86e00bbde8950364f8e7846687259ffcd96e8c2" + integrity sha512-lPG6KlZs88gef6aD85z3HNkztpj7w2R7HmR3gygjfXCQmsLloWNARFkMuzKiiY8FGdh1XDpgBdrSf4aKDiA7Kg== "@types/http-proxy@^1.17.8": - version "1.17.11" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.11.tgz#0ca21949a5588d55ac2b659b69035c84bd5da293" - integrity sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA== + version "1.17.12" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.12.tgz#86e849e9eeae0362548803c37a0a1afc616bd96b" + integrity sha512-kQtujO08dVtQ2wXAuSFfk9ASy3sug4+ogFR8Kd8UgP8PEuc1/G/8yjYRmp//PcDNJEUKOza/MrQu15bouEUCiw== dependencies: "@types/node" "*" @@ -1970,9 +1833,9 @@ "@types/istanbul-lib-report" "*" "@types/json-schema@*", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": - version "7.0.12" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" - integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== + version "7.0.13" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.13.tgz#02c24f4363176d2d18fc8b70b9f3c54aba178a85" + integrity sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ== "@types/mime@*": version "3.0.1" @@ -1990,15 +1853,9 @@ integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== "@types/node@*": -<<<<<<< HEAD - version "20.5.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.5.9.tgz#a70ec9d8fa0180a314c3ede0e20ea56ff71aed9a" - integrity sha512-PcGNd//40kHAS3sTlzKB9C9XL4K0sTup8nbG5lC14kzEteTNuAFh9u5nA0o5TWnSG2r/JNPRXFVcHJIIeRlmqQ== -======= - version "20.6.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.6.0.tgz#9d7daa855d33d4efec8aea88cd66db1c2f0ebe16" - integrity sha512-najjVq5KN2vsH2U/xyh2opaSEz6cZMR2SetLIlxlj08nOcmPOemJmUK2o4kUzfLqfrWE0PIrNeE16XhYDd3nqg== ->>>>>>> master + version "20.6.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.6.2.tgz#a065925409f59657022e9063275cd0b9bd7e1b12" + integrity sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw== "@types/parse-json@^4.0.0": version "4.0.0" @@ -2577,9 +2434,9 @@ bootbox@^6.0.0: integrity sha512-+Calbj1v5UvxAXXDAHfoBlsx63Hcz1JqHaZdJ5EjIcOlkyAbZLCreVScx0Em6ZUvsMCqynuz/3nGDyd9FtFrNQ== bootstrap@^5.1.3: - version "5.3.1" - resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.1.tgz#8ca07040ad15d7f75891d1504cf14c5dedfb1cfe" - integrity sha512-jzwza3Yagduci2x0rr9MeFSORjcHpt0lRZukZPZQJT1Dth5qzV7XcgGqYzi39KGAVYR8QEDVoO0ubFKOxzMG+g== + version "5.3.2" + resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.2.tgz#97226583f27aae93b2b28ab23f4c114757ff16ae" + integrity sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g== bootswatch@^5.1.3: version "5.3.1" @@ -2732,15 +2589,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001517: -<<<<<<< HEAD - version "1.0.30001525" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001525.tgz#d2e8fdec6116ffa36284ca2c33ef6d53612fe1c8" - integrity sha512-/3z+wB4icFt3r0USMwxujAqRvaD/B7rvGTsKhbhSQErVrJvkZCLhgNLJxU8MevahQVH6hCU9FsHdNUFbiwmE7Q== -======= - version "1.0.30001532" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001532.tgz#c6a4d5d2da6d2b967f0ee5e12e7f680db6ad2fca" - integrity sha512-FbDFnNat3nMnrROzqrsg314zhqN5LGQ1kyyMk2opcrwGbVGpHRhgCWtAgD5YJUqNAiQ+dklreil/c3Qf1dfCTw== ->>>>>>> master + version "1.0.30001534" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001534.tgz#f24a9b2a6d39630bac5c132b5dff89b39a12e7dd" + integrity sha512-vlPVrhsCS7XaSh2VvWluIQEzVhefrUQcEsQWSS5A5V+dM07uv1qHeQzAOTGIMy9i3e9bH15+muvI/UHojVgS/Q== chalk@^2.3.2, chalk@^2.4.2: version "2.4.2" @@ -2836,9 +2687,9 @@ cli-cursor@^3.1.0: restore-cursor "^3.1.0" cli-spinners@^2.6.1: - version "2.9.0" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.0.tgz#5881d0ad96381e117bbe07ad91f2008fe6ffd8db" - integrity sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g== + version "2.9.1" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.1.tgz#9c0b9dad69a6d47cbb4333c14319b060ed395a35" + integrity sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ== clipboard@^2.0.4: version "2.0.11" @@ -3449,16 +3300,26 @@ default-gateway@^6.0.3: dependencies: execa "^5.0.0" +define-data-property@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.0.tgz#0db13540704e1d8d479a0656cf781267531b9451" + integrity sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g== + dependencies: + get-intrinsic "^1.2.1" + gopd "^1.0.1" + has-property-descriptors "^1.0.0" + define-lazy-prop@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== define-properties@^1.1.3, define-properties@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5" - integrity sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA== + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== dependencies: + define-data-property "^1.0.1" has-property-descriptors "^1.0.0" object-keys "^1.1.1" @@ -3635,15 +3496,9 @@ ee-first@1.1.1: integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== electron-to-chromium@^1.4.477: -<<<<<<< HEAD - version "1.4.508" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.508.tgz#5641ff2f5ba11df4bd960fe6a2f9f70aa8b9af96" - integrity sha512-FFa8QKjQK/A5QuFr2167myhMesGrhlOBD+3cYNxO9/S4XzHEXesyTD/1/xF644gC8buFPz3ca6G1LOQD0tZrrg== -======= - version "1.4.513" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.513.tgz#41a50bf749aa7d8058ffbf7a131fc3327a7b1675" - integrity sha512-cOB0xcInjm+E5qIssHeXJ29BaUyWpMyFKT5RB3bsLENDheCja0wMkHJyiPl0NBE/VzDI7JDuNEQWhe6RitEUcw== ->>>>>>> master + version "1.4.523" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.523.tgz#f82f99243c827df05c26776d49712cb284972df6" + integrity sha512-9AreocSUWnzNtvLcbpng6N+GkXnCcBR80IQkxRC9Dfdyg4gaWNUPBujAHUpKkiUkoSoR9UlhA4zD/IgBklmhzg== emoji-regex@^8.0.0: version "8.0.0" @@ -3703,9 +3558,9 @@ error-stack-parser@^2.0.0: stackframe "^1.3.4" es-module-lexer@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.3.0.tgz#6be9c9e0b4543a60cd166ff6f8b4e9dae0b0c16f" - integrity sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA== + version "1.3.1" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.3.1.tgz#c1b0dd5ada807a3b3155315911f364dc4e909db1" + integrity sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q== es5-ext@^0.10.35, es5-ext@^0.10.50, es5-ext@^0.10.62, es5-ext@~0.10.14: version "0.10.62" @@ -4165,7 +4020,7 @@ get-assigned-identifiers@^1.1.0: resolved "https://registry.yarnpkg.com/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz#6dbf411de648cbaf8d9169ebb0d2d576191e2ff1" integrity sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ== -get-intrinsic@^1.0.2, get-intrinsic@^1.1.1: +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== @@ -4258,6 +4113,13 @@ good-listener@^1.2.2: dependencies: delegate "^3.1.2" +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" @@ -4520,13 +4382,13 @@ interpret@^2.2.0: integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== intl-messageformat@^10.2.5: - version "10.5.1" - resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.5.1.tgz#40304cbde01c8cb2236e11ac8c827642bed474d0" - integrity sha512-irEmjxHq0f1MHviQr3Q4ToF9EgYbnXDq2/R9MRTTveGKHgy6VZ29hQxswu4trqWaX7T6njKxSoKVG92OSz0U5Q== + version "10.5.2" + resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.5.2.tgz#eb746b4b895bd37e8ef91be7b488e450aa4e9d7b" + integrity sha512-X4rlUNbgCc8/RdMhmvUEEZ38yNDn5S4r0u8n8yQH2OOdhsR46SmOuQsCKG35nRXmL5u2nxPsNN6qNhHoMm6FMQ== dependencies: - "@formatjs/ecma402-abstract" "1.17.1" + "@formatjs/ecma402-abstract" "1.17.2" "@formatjs/fast-memoize" "2.2.0" - "@formatjs/icu-messageformat-parser" "2.6.1" + "@formatjs/icu-messageformat-parser" "2.6.2" tslib "^2.4.0" ipaddr.js@1.9.1: @@ -4691,10 +4553,10 @@ javascript-stringify@^1.6.0: resolved "https://registry.yarnpkg.com/javascript-stringify/-/javascript-stringify-1.6.0.tgz#142d111f3a6e3dae8f4a9afd77d45855b5a9cce3" integrity sha512-fnjC0up+0SjEJtgmmG+teeel68kutkvzfctO/KxE3qJlbunkJYAshgH3boU++gSBHP8z5/r0ts0qRIrHf0RTQQ== -jest-util@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.6.3.tgz#e15c3eac8716440d1ed076f09bc63ace1aebca63" - integrity sha512-QUjna/xSy4B32fzcKTSz1w7YYzgiHrjjJjevdRf61HYk998R5vVMMNmrHESYZVDS5DSWs+1srPLPKxXPkeSDOA== +jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== dependencies: "@jest/types" "^29.6.3" "@types/node" "*" @@ -4722,12 +4584,12 @@ jest-worker@^27.4.5: supports-color "^8.0.0" jest-worker@^29.4.3: - version "29.6.4" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.6.4.tgz#f34279f4afc33c872b470d4af21b281ac616abd3" - integrity sha512-6dpvFV4WjcWbDVGgHTWo/aupl8/LbBx2NSKfiwqf79xC/yeJjKHT1+StcKy/2KTmW16hE68ccKVOtXf+WZGz7Q== + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== dependencies: "@types/node" "*" - jest-util "^29.6.3" + jest-util "^29.7.0" merge-stream "^2.0.0" supports-color "^8.0.0" @@ -4986,16 +4848,16 @@ make-dir@^3.0.2, make-dir@^3.1.0: semver "^6.0.0" marked-gfm-heading-id@^3.0.4: - version "3.0.7" - resolved "https://registry.yarnpkg.com/marked-gfm-heading-id/-/marked-gfm-heading-id-3.0.7.tgz#99210e81fbf1ceab2239c18ef365e1ce358c9849" - integrity sha512-8JtFnp8/50qwpwwt446Uv4Ohy0jDLNGnAB7sWln7SLQcQQV7+K3qlMMwYupELz9cZvKKiRqXHEGNopcEvxM2ew== + version "3.0.8" + resolved "https://registry.yarnpkg.com/marked-gfm-heading-id/-/marked-gfm-heading-id-3.0.8.tgz#7d77f264181633b78c7fcb522b1df46783689a64" + integrity sha512-JDKp7tqU0QYHGz9xBWiZGfiCPNcvg0PvNvRr6XvRTgIg4S4etZxns7+ez+YtREU8iuQJC0V6vLTae9n8FH6gxQ== dependencies: github-slugger "^2.0.0" marked-mangle@^1.0.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/marked-mangle/-/marked-mangle-1.1.3.tgz#4f22f712a92f8d108f5be5d621e38e82067e1d7d" - integrity sha512-eVstXDoQzXZZ3tHDlCEEI0wGdequP1a5cBg04V7DF+pu2tso5QZAwJHGiMfDOFEo501TCW2PmKNjIaSdD3T5HQ== + version "1.1.4" + resolved "https://registry.yarnpkg.com/marked-mangle/-/marked-mangle-1.1.4.tgz#7f0d3af2430880148ee604be7440624b06772b4b" + integrity sha512-O9Fc08hX/9EYjXevrVKphjPQE4GumC5TqW7ret+/It6NS+LJVItRz1WlF3WVyj9siwSO8bfCxEpf5sxdT3TsLw== marked@4.0.12: version "4.0.12" @@ -6189,9 +6051,9 @@ rechoir@^0.7.0: resolve "^1.9.0" regenerate-unicode-properties@^10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c" - integrity sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ== + version "10.1.1" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz#6b0e05489d9076b04c436f318d9b067bba459480" + integrity sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q== dependencies: regenerate "^1.4.2" @@ -6223,13 +6085,13 @@ regex-parser@^2.2.11: integrity sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q== regexp.prototype.flags@^1.2.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz#fe7ce25e7e4cca8db37b6634c8a2c7009199b9cb" - integrity sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA== + version "1.5.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e" + integrity sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg== dependencies: call-bind "^1.0.2" define-properties "^1.2.0" - functions-have-names "^1.2.3" + set-function-name "^2.0.0" regexpu-core@^5.3.1: version "5.3.2" @@ -6310,9 +6172,9 @@ resolve@1.1.7: integrity sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg== resolve@^1.1.5, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.14.2, resolve@^1.9.0: - version "1.22.4" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.4.tgz#1dc40df46554cdaf8948a486a10f6ba1e2026c34" - integrity sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg== + version "1.22.6" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.6.tgz#dd209739eca3aef739c626fea1b4f3c506195362" + integrity sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw== dependencies: is-core-module "^2.13.0" path-parse "^1.0.7" @@ -6510,6 +6372,15 @@ serve-static@1.15.0: parseurl "~1.3.3" send "0.18.0" +set-function-name@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" + integrity sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA== + dependencies: + define-data-property "^1.0.1" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.0" + setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" @@ -6903,15 +6774,9 @@ terser-webpack-plugin@^5.3.0, terser-webpack-plugin@^5.3.7: terser "^5.16.8" terser@^5.16.8, terser@^5.3.4: -<<<<<<< HEAD - version "5.19.3" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.19.3.tgz#359baeba615aef13db4b8c4d77a2aa0d8814aa9e" - integrity sha512-pQzJ9UJzM0IgmT4FAtYI6+VqFf0lj/to58AV0Xfgg0Up37RyPG7Al+1cepC6/BVuAxR9oNb41/DL4DEoHJvTdg== -======= version "5.19.4" resolved "https://registry.yarnpkg.com/terser/-/terser-5.19.4.tgz#941426fa482bf9b40a0308ab2b3cd0cf7c775ebd" integrity sha512-6p1DjHeuluwxDXcuT9VR8p64klWJKo1ILiy19s6C9+0Bh2+NWTX6nD9EPppiER4ICkHDVB1RkVpin/YW2nQn/g== ->>>>>>> master dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.8.2" From 219fbe5fcabb0e01e0669ead4df5f84544db4f1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 17 Sep 2023 11:24:33 +0200 Subject: [PATCH 28/62] Added API endpoints for orderdetails and pricedetails --- src/Entity/PriceInformations/Orderdetail.php | 43 +++++++++++++++++--- src/Entity/PriceInformations/Pricedetail.php | 29 +++++++++++-- 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/src/Entity/PriceInformations/Orderdetail.php b/src/Entity/PriceInformations/Orderdetail.php index d61eeb68..cfa81b01 100644 --- a/src/Entity/PriceInformations/Orderdetail.php +++ b/src/Entity/PriceInformations/Orderdetail.php @@ -23,6 +23,15 @@ declare(strict_types=1); namespace App\Entity\PriceInformations; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Serializer\Filter\PropertyFilter; use Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractDBElement; use App\Entity\Base\TimestampTrait; @@ -46,12 +55,35 @@ use Symfony\Component\Validator\Constraints as Assert; #[ORM\HasLifecycleCallbacks] #[ORM\Table('`orderdetails`')] #[ORM\Index(name: 'orderdetails_supplier_part_nr', columns: ['supplierpartnr'])] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@parts.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['orderdetail:read', 'api:basic:read', 'pricedetail:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['orderdetail:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiResource( + uriTemplate: '/parts/{id}/orderdetails.{_format}', + operations: [ + new GetCollection(openapiContext: ['summary' => 'Retrieves the orderdetails of a part.'], + security: 'is_granted("@parts.read")') + ], + uriVariables: [ + 'id' => new Link(toProperty: 'part', fromClass: Part::class) + ], + normalizationContext: ['groups' => ['orderdetail:read', 'pricedetail:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] +)] +#[ApiFilter(PropertyFilter::class)] class Orderdetail extends AbstractDBElement implements TimeStampableInterface, NamedElementInterface { use TimestampTrait; #[Assert\Valid] - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'orderdetail:read', 'orderdetail:write'])] #[ORM\OneToMany(targetEntity: Pricedetail::class, mappedBy: 'orderdetail', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['min_discount_quantity' => 'ASC'])] protected Collection $pricedetails; @@ -59,14 +91,14 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N /** * @var string */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'orderdetail:read', 'orderdetail:write'])] #[ORM\Column(type: Types::STRING)] protected string $supplierpartnr = ''; /** * @var bool */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'orderdetail:read', 'orderdetail:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $obsolete = false; @@ -74,7 +106,7 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N * @var string */ #[Assert\Url] - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'orderdetail:read', 'orderdetail:write'])] #[ORM\Column(type: Types::TEXT)] protected string $supplier_product_url = ''; @@ -83,6 +115,7 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N */ #[Assert\NotNull] #[ORM\ManyToOne(targetEntity: Part::class, inversedBy: 'orderdetails')] + #[Groups(['orderdetail:read', 'orderdetail:write'])] #[ORM\JoinColumn(name: 'part_id', nullable: false, onDelete: 'CASCADE')] protected ?Part $part = null; @@ -90,7 +123,7 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N * @var Supplier|null */ #[Assert\NotNull(message: 'validator.orderdetail.supplier_must_not_be_null')] - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'orderdetail:read', 'orderdetail:write'])] #[ORM\ManyToOne(targetEntity: Supplier::class, inversedBy: 'orderdetails')] #[ORM\JoinColumn(name: 'id_supplier')] protected ?Supplier $supplier = null; diff --git a/src/Entity/PriceInformations/Pricedetail.php b/src/Entity/PriceInformations/Pricedetail.php index 26afbb50..2afe02e9 100644 --- a/src/Entity/PriceInformations/Pricedetail.php +++ b/src/Entity/PriceInformations/Pricedetail.php @@ -22,6 +22,14 @@ declare(strict_types=1); namespace App\Entity\PriceInformations; +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 Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractDBElement; use App\Entity\Base\TimestampTrait; @@ -45,6 +53,18 @@ use Symfony\Component\Validator\Constraints as Assert; #[ORM\Table('`pricedetails`')] #[ORM\Index(name: 'pricedetails_idx_min_discount', columns: ['min_discount_quantity'])] #[ORM\Index(name: 'pricedetails_idx_min_discount_price_qty', columns: ['min_discount_quantity', 'price_related_quantity'])] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@parts.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['pricedetail:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['pricedetail:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiFilter(PropertyFilter::class)] class Pricedetail extends AbstractDBElement implements TimeStampableInterface { use TimestampTrait; @@ -54,7 +74,7 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface /** * @var BigDecimal The price related to the detail. (Given in the selected currency) */ - #[Groups(['extended', 'full'])] + #[Groups(['extended', 'full', 'pricedetail:read', 'pricedetail:write'])] #[ORM\Column(type: 'big_decimal', precision: 11, scale: 5)] #[BigDecimalPositive()] protected BigDecimal $price; @@ -63,7 +83,7 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface * @var ?Currency The currency used for the current price information. * If this is null, the global base unit is assumed */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'pricedetail:read', 'pricedetail:write'])] #[ORM\ManyToOne(targetEntity: Currency::class, inversedBy: 'pricedetails')] #[ORM\JoinColumn(name: 'id_currency')] #[Selectable()] @@ -73,7 +93,7 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface * @var float */ #[Assert\Positive] - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'pricedetail:read', 'pricedetail:write'])] #[ORM\Column(type: Types::FLOAT)] protected float $price_related_quantity = 1.0; @@ -81,7 +101,7 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface * @var float */ #[Assert\Positive] - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'pricedetail:read', 'pricedetail:write'])] #[ORM\Column(type: Types::FLOAT)] protected float $min_discount_quantity = 1.0; @@ -97,6 +117,7 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface #[Assert\NotNull] #[ORM\ManyToOne(targetEntity: Orderdetail::class, inversedBy: 'pricedetails')] #[ORM\JoinColumn(name: 'orderdetails_id', nullable: false, onDelete: 'CASCADE')] + #[Groups(['pricedetail:read', 'pricedetail:write'])] protected ?Orderdetail $orderdetail = null; public function __construct() From f01ec9dbe46d2d9ad7d86858dbcd6202f9b6f84c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 17 Sep 2023 12:50:32 +0200 Subject: [PATCH 29/62] Improved documentation of entity fields --- src/Entity/Base/AbstractCompany.php | 2 +- src/Entity/Base/AbstractNamedDBElement.php | 2 +- .../Base/AbstractPartsContainingDBElement.php | 3 +++ src/Entity/Parts/Category.php | 16 ++++++++-------- src/Entity/Parts/PartLot.php | 2 +- .../Parts/PartTraits/AdvancedPropertyTrait.php | 4 ++-- src/Entity/Parts/PartTraits/InstockTrait.php | 2 +- .../Parts/PartTraits/ManufacturerTrait.php | 2 +- src/Entity/Parts/PartTraits/OrderTrait.php | 2 +- src/Entity/Parts/StorageLocation.php | 6 +++--- src/Entity/Parts/Supplier.php | 2 +- src/Entity/PriceInformations/Orderdetail.php | 10 +++++----- src/Entity/PriceInformations/Pricedetail.php | 4 ++-- 13 files changed, 30 insertions(+), 27 deletions(-) diff --git a/src/Entity/Base/AbstractCompany.php b/src/Entity/Base/AbstractCompany.php index e1f5e757..3b6e6ccd 100644 --- a/src/Entity/Base/AbstractCompany.php +++ b/src/Entity/Base/AbstractCompany.php @@ -86,7 +86,7 @@ abstract class AbstractCompany extends AbstractPartsContainingDBElement protected string $comment = ''; /** - * @var string + * @var string The link to the website of an article. Use %PARTNUMBER% as placeholder for the part number. */ #[ORM\Column(type: Types::STRING)] protected string $auto_product_url = ''; diff --git a/src/Entity/Base/AbstractNamedDBElement.php b/src/Entity/Base/AbstractNamedDBElement.php index c7f5f872..d109b71a 100644 --- a/src/Entity/Base/AbstractNamedDBElement.php +++ b/src/Entity/Base/AbstractNamedDBElement.php @@ -40,7 +40,7 @@ abstract class AbstractNamedDBElement extends AbstractDBElement implements Named use TimestampTrait; /** - * @var string the name of this element + * @var string The name of this element */ #[Assert\NotBlank] #[Groups(['simple', 'extended', 'full', 'import', 'api:basic:read', 'api:basic:write'])] diff --git a/src/Entity/Base/AbstractPartsContainingDBElement.php b/src/Entity/Base/AbstractPartsContainingDBElement.php index 5d25283e..3ea1c216 100644 --- a/src/Entity/Base/AbstractPartsContainingDBElement.php +++ b/src/Entity/Base/AbstractPartsContainingDBElement.php @@ -40,6 +40,9 @@ use Symfony\Component\Serializer\Annotation\Groups; #[ORM\MappedSuperclass(repositoryClass: AbstractPartsContainingRepository::class)] abstract class AbstractPartsContainingDBElement extends AbstractStructuralDBElement { + /** + * @var Collection|ArrayCollection The parameters of this element + */ #[Groups(['full'])] protected Collection $parameters; diff --git a/src/Entity/Parts/Category.php b/src/Entity/Parts/Category.php index c1d2e917..b1714fe9 100644 --- a/src/Entity/Parts/Category.php +++ b/src/Entity/Parts/Category.php @@ -93,56 +93,56 @@ class Category extends AbstractPartsContainingDBElement protected string $comment = ''; /** - * @var string + * @var string The hint which is shown as hint under the partname field, when a part is created in this category. */ #[Groups(['full', 'import', 'category:read', 'category:write'])] #[ORM\Column(type: Types::TEXT)] protected string $partname_hint = ''; /** - * @var string + * @var string The regular expression which is used to validate the partname of a part in this category. */ #[Groups(['full', 'import', 'category:read', 'category:write'])] #[ORM\Column(type: Types::TEXT)] protected string $partname_regex = ''; /** - * @var bool + * @var bool Set to true, if the footprints should be disabled for parts this category (not implemented yet). */ #[Groups(['full', 'import', 'category:read', 'category:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $disable_footprints = false; /** - * @var bool + * @var bool Set to true, if the manufacturers should be disabled for parts this category (not implemented yet). */ #[Groups(['full', 'import', 'category:read', 'category:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $disable_manufacturers = false; /** - * @var bool + * @var bool Set to true, if the autodatasheets should be disabled for parts this category (not implemented yet). */ #[Groups(['full', 'import', 'category:read', 'category:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $disable_autodatasheets = false; /** - * @var bool + * @var bool Set to true, if the properties should be disabled for parts this category (not implemented yet). */ #[Groups(['full', 'import', 'category:read', 'category:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $disable_properties = false; /** - * @var string + * @var string The default description for parts in this category. */ #[Groups(['full', 'import', 'category:read', 'category:write'])] #[ORM\Column(type: Types::TEXT)] protected string $default_description = ''; /** - * @var string + * @var string The default comment for parts in this category. */ #[Groups(['full', 'import', 'category:read', 'category:write'])] #[ORM\Column(type: Types::TEXT)] diff --git a/src/Entity/Parts/PartLot.php b/src/Entity/Parts/PartLot.php index f026ddec..7bf85d6b 100644 --- a/src/Entity/Parts/PartLot.php +++ b/src/Entity/Parts/PartLot.php @@ -113,7 +113,7 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named protected bool $instock_unknown = false; /** - * @var float For continuous sizes (length, volume, etc.) the instock is saved here. + * @var float The amount of parts in this lot. For integer-quantities this value is rounded to the next integer. */ #[Assert\PositiveOrZero] #[Groups(['simple', 'extended', 'full', 'import', 'part_lot:read', 'part_lot:write'])] diff --git a/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php b/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php index 712d1c2a..2c5e6040 100644 --- a/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php +++ b/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php @@ -43,14 +43,14 @@ trait AdvancedPropertyTrait protected bool $needs_review = false; /** - * @var string a comma separated list of tags, associated with the part + * @var string A comma separated list of tags, associated with the part */ #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::TEXT)] protected string $tags = ''; /** - * @var float|null how much a single part unit weighs in grams + * @var float|null How much a single part unit weighs in grams */ #[Assert\PositiveOrZero] #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] diff --git a/src/Entity/Parts/PartTraits/InstockTrait.php b/src/Entity/Parts/PartTraits/InstockTrait.php index f8d27cb3..0a031c55 100644 --- a/src/Entity/Parts/PartTraits/InstockTrait.php +++ b/src/Entity/Parts/PartTraits/InstockTrait.php @@ -36,7 +36,7 @@ use Symfony\Component\Validator\Constraints as Assert; trait InstockTrait { /** - * @var Collection|PartLot[] A list of part lots where this part is stored + * @var Collection A list of part lots where this part is stored */ #[Assert\Valid] #[Groups(['extended', 'full', 'import'])] diff --git a/src/Entity/Parts/PartTraits/ManufacturerTrait.php b/src/Entity/Parts/PartTraits/ManufacturerTrait.php index 12643768..329041ee 100644 --- a/src/Entity/Parts/PartTraits/ManufacturerTrait.php +++ b/src/Entity/Parts/PartTraits/ManufacturerTrait.php @@ -46,7 +46,7 @@ trait ManufacturerTrait protected ?Manufacturer $manufacturer = null; /** - * @var string the url to the part on the manufacturer's homepage + * @var string The url to the part on the manufacturer's homepage */ #[Assert\Url] #[Groups(['full', 'import', 'part:read', 'part:write'])] diff --git a/src/Entity/Parts/PartTraits/OrderTrait.php b/src/Entity/Parts/PartTraits/OrderTrait.php index a442298c..3b2f143c 100644 --- a/src/Entity/Parts/PartTraits/OrderTrait.php +++ b/src/Entity/Parts/PartTraits/OrderTrait.php @@ -36,7 +36,7 @@ use Doctrine\ORM\Mapping as ORM; trait OrderTrait { /** - * @var Collection the details about how and where you can order this part + * @var Collection The details about how and where you can order this part */ #[Assert\Valid] #[Groups(['extended', 'full', 'import'])] diff --git a/src/Entity/Parts/StorageLocation.php b/src/Entity/Parts/StorageLocation.php index 70ca1467..c21d3f28 100644 --- a/src/Entity/Parts/StorageLocation.php +++ b/src/Entity/Parts/StorageLocation.php @@ -108,21 +108,21 @@ class StorageLocation extends AbstractPartsContainingDBElement protected Collection $parameters; /** - * @var bool + * @var bool When this attribute is set, it is not possible to add additional parts or increase the instock of existing parts. */ #[Groups(['full', 'import', 'location:read', 'location:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $is_full = false; /** - * @var bool + * @var bool When this property is set, only one part (but many instock) is allowed to be stored in this store location. */ #[Groups(['full', 'import', 'location:read', 'location:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $only_single_part = false; /** - * @var bool + * @var bool When this property is set, it is only possible to increase the instock of parts, that are already stored here. */ #[Groups(['full', 'import', 'location:read', 'location:write'])] #[ORM\Column(type: Types::BOOLEAN)] diff --git a/src/Entity/Parts/Supplier.php b/src/Entity/Parts/Supplier.php index fb0a731c..749d3a30 100644 --- a/src/Entity/Parts/Supplier.php +++ b/src/Entity/Parts/Supplier.php @@ -108,7 +108,7 @@ class Supplier extends AbstractCompany protected ?Currency $default_currency = null; /** - * @var BigDecimal|null the shipping costs that have to be paid, when ordering via this supplier + * @var BigDecimal|null The shipping costs that have to be paid, when ordering via this supplier */ #[Groups(['extended', 'full', 'import'])] #[ORM\Column(name: 'shipping_costs', nullable: true, type: 'big_decimal', precision: 11, scale: 5)] diff --git a/src/Entity/PriceInformations/Orderdetail.php b/src/Entity/PriceInformations/Orderdetail.php index cfa81b01..50b2653b 100644 --- a/src/Entity/PriceInformations/Orderdetail.php +++ b/src/Entity/PriceInformations/Orderdetail.php @@ -89,21 +89,21 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N protected Collection $pricedetails; /** - * @var string + * @var string The order number of the part at the supplier */ #[Groups(['extended', 'full', 'import', 'orderdetail:read', 'orderdetail:write'])] #[ORM\Column(type: Types::STRING)] protected string $supplierpartnr = ''; /** - * @var bool + * @var bool True if this part is obsolete/not available anymore at the supplier */ #[Groups(['extended', 'full', 'import', 'orderdetail:read', 'orderdetail:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $obsolete = false; /** - * @var string + * @var string The URL to the product on the supplier's website */ #[Assert\Url] #[Groups(['full', 'import', 'orderdetail:read', 'orderdetail:write'])] @@ -111,7 +111,7 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N protected string $supplier_product_url = ''; /** - * @var Part|null + * @var Part|null The part with which this orderdetail is associated */ #[Assert\NotNull] #[ORM\ManyToOne(targetEntity: Part::class, inversedBy: 'orderdetails')] @@ -120,7 +120,7 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N protected ?Part $part = null; /** - * @var Supplier|null + * @var Supplier|null The supplier of this orderdetail */ #[Assert\NotNull(message: 'validator.orderdetail.supplier_must_not_be_null')] #[Groups(['extended', 'full', 'import', 'orderdetail:read', 'orderdetail:write'])] diff --git a/src/Entity/PriceInformations/Pricedetail.php b/src/Entity/PriceInformations/Pricedetail.php index 2afe02e9..b27fe9cc 100644 --- a/src/Entity/PriceInformations/Pricedetail.php +++ b/src/Entity/PriceInformations/Pricedetail.php @@ -90,7 +90,7 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface protected ?Currency $currency = null; /** - * @var float + * @var float The amount/quantity for which the price is for (in part unit) */ #[Assert\Positive] #[Groups(['extended', 'full', 'import', 'pricedetail:read', 'pricedetail:write'])] @@ -98,7 +98,7 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface protected float $price_related_quantity = 1.0; /** - * @var float + * @var float The minimum amount/quantity, which is needed to get this discount (in part unit) */ #[Assert\Positive] #[Groups(['extended', 'full', 'import', 'pricedetail:read', 'pricedetail:write'])] From 6387c24c89d5d99cf40669af0d3f2ff597fe9072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 18 Sep 2023 12:48:16 +0200 Subject: [PATCH 30/62] Added some documentation to the API --- docs/api/authentication.md | 61 +++++++++++++++++++++++++ docs/api/index.md | 11 +++++ docs/api/intro.md | 71 ++++++++++++++++++++++++++++++ src/Entity/UserSystem/ApiToken.php | 2 +- 4 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 docs/api/authentication.md create mode 100644 docs/api/index.md create mode 100644 docs/api/intro.md diff --git a/docs/api/authentication.md b/docs/api/authentication.md new file mode 100644 index 00000000..50d57743 --- /dev/null +++ b/docs/api/authentication.md @@ -0,0 +1,61 @@ +--- +title: Authentication +layout: default +parent: API +nav_order: 2 +--- + +# Authentication + +To use API endpoints, the external application has to authenticate itself, so that Part-DB knows which user is accessing the data and which permissions +the application should have during the access. Authentication is always bound to a specific user, so the external applications is acting on behalf of a +specific user. This user limits the permissions of the application, so that it can only access data, which the user is allowed to access. + +The only method currently available for authentication is to use API tokens: + +## API tokens + +An API token is a long alphanumeric string, which is bound to a specific user and can be used to authenticate as this user, when accessing the API. +The API token is passed via the `Authentication` HTTP header during the API request, like the following: `Authentication: Bearer tcp_sdjfks....`. + +{: .important } +> Everbody who knows the API token can access the API as the user, which is bound to the token. So you should treat the API token like a password +> and keep it secret. Only share it with trusted applications. + +API tokens can be created and managed on the user settings page in the API token section. You can create as many API tokens as you want and also delete them again. +When deleting a token, it is immediately invalidated and can not be used anymore, which means that the application can not access the API anymore with this token. + + +### Token permissions and scopes + +API tokens are ultimately limited by the permissions of the user, which belongs to the token. That means that the token +can only access data, which the user is allowed to access, no matter the token permissions. + +But you can further limit the permissions of a token by choosing a specific scope for the token. The scope defines which +subset of permissions the token has, which can be less than the permissions of the user. For example you can have a user +with full read and write permissions, but create a token with only read permissions, which can only read data, but not +change anything in the database. + +{: .warning } +> In general you should always use the least possible permissions for a token, to limit the possible damage, which can be done with a stolen token or a bug in the application. +> Only use the full or admin scope, if you really need it, as they could potentially be used to do a lot of damage to your Part-DB instance. + +Following token scopes are available: + +* **Read-Only**: The token can only read non-sensitive data (like parts, but no users or groups) from the API and can not change anything. +* **Edit**: The token can read and write non-sensitive data via the API. This includes creating, updating and deleting data. This should be enough for most applications. +* **Admin**: The token can read and write all data via the API, including sensitive data like users and groups. This should only be used for trusted applications, which need to access sensitive data, and perform administrative actions. +* **Full**: The token can do anything the user can do, including changing the users password and create new tokens. This should only be used for highly trusted applications!! + +Please note, that in early versions of the API, there might be no endpoints yet, to really perform the actions, which would be allowed by the token scope. + +### Expiration date + +API tokens can have an expiration date, which means that the token is only valid until the expiration date. After that +the token is automatically invalidated and can not be used anymore. The token is still listed on the user settings page, +and can be deleted there, but the code can not be used to access Part-DB anymore after the expiration date. + +### Get token information + +When authenticating with an API token, you can get information about the currently used token by accessing the `/api/tokens/current` endpoint. +It gives you information about the token scope, expiration date and the user, which is bound to the token and the last time the token was used. \ No newline at end of file diff --git a/docs/api/index.md b/docs/api/index.md new file mode 100644 index 00000000..441ede9a --- /dev/null +++ b/docs/api/index.md @@ -0,0 +1,11 @@ +--- +layout: default +title: API +nav_order: 7 +has_children: true +--- + +# API + +Part-DB provides a REST API to access the data stored in the database. +In this section you can find information about the API and how to use it. diff --git a/docs/api/intro.md b/docs/api/intro.md new file mode 100644 index 00000000..b7ec60ae --- /dev/null +++ b/docs/api/intro.md @@ -0,0 +1,71 @@ +--- +title: Introduction +layout: default +parent: API +nav_order: 1 +--- + +# Introduction + +Part-DB provides a [REST API](https://en.wikipedia.org/wiki/REST) to programmatically access the data stored in the database. +This allows external applications to interact with Part-DB, extend it or integrate it into other applications. + +{: .warning } +> This feature is currently in beta. Please report any bugs you find. +> The API should not be considered stable yet and could change in future versions, without prior notice. +> 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 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 with Symfony and gives you a lot of features out of the box. +See the [API Platform documentation](https://api-platform.com/docs/core/) for more details about the API Platform features and how to use them. + +## Enable the API + +The API is available under the `/api` path, but not reachable without proper permissions. +You have to give the users, which should be able to access the API the proper permissions (Misceallaneous -> API). +Please note that there are two relevant permissions, the first one allows users to access the `/api/` path at all and showing the documentation, +and the second one allows them to create API tokens which is needed for authentication of external applications. + +## Authentication + +To use API endpoints, the external application has to authenticate itself, so that Part-DB knows which user is accessing the data and +which permisions the application should have. Basically this is done by creating a API token for a user and then passing it on every request +with the `Authorization` header as bearer token, so you add a header `Authorization: Bearer `. + +See [Authentication chapter]({% link api/authentication.md %}) for more details. + +## API endpoints + +The API is split into different endpoints, which are reachable under the `/api/` path of your Part-DB instance (so `https://your-part-db.local/api/`). +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. +For most entities, there are endpoints like this: +* **GET**: `/api/categories/` - List all categories in the database (with pagination of the results) +* **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 +* **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 Patch](https://en.wikipedia.org/wiki/JSON_Patch) content type header (`Content-Type: application/merge-patch+json`) for this to work. + +A full (interactive) list of endpoints can be displayed when visiting the `/api/` path in your browser, when you are logged in with a user, which is allowed to access the API. +There is also a link to this page, on the user settings page in the API token section. +This documentation also list all available fields for each entity type and the allowed operations. + +## Formats + +The API supports different formats for the request and response data, which you can control via the `Accept` and `Content-Type` headers. +You should use [JSON-LD](https://json-ld.org/) as format, which is basically JSON with some additional metadata, which allows +to describe the data in a more structured way and also allows to link between different entities. You can achieve this by setting `Accept: application/ld+json` header to the API requests. + +## OpenAPI schema + +Part-DB provides a [OpenAPI](https://swagger.io/specification/) (formally Swagger) schema for the API under `/api/docs.json` (so `https://your-part-db.local/api/docs.json`). +This schema is a machine readable description of the API, which can be imported in software to test the API or even automatically generate client libraries for the API. + +API generators which can generate a client library for the API from the schema are available for many programming languages, like [OpenAPI Generator](https://openapi-generator.tech/). + +## Interactive documentation + +Part-DB provides an interactive documentation for the API, which is available under `/api/docs` (so `https://your-part-db.local/api/docs`). +You can pass your API token in the form on the top of the page, to authenticate yourself and then you can try out the API directly in the browser. +This is a great way to test the API and see how it works, without having to write any code. \ No newline at end of file diff --git a/src/Entity/UserSystem/ApiToken.php b/src/Entity/UserSystem/ApiToken.php index e344e464..b46c5f8b 100644 --- a/src/Entity/UserSystem/ApiToken.php +++ b/src/Entity/UserSystem/ApiToken.php @@ -44,7 +44,7 @@ use Symfony\Component\Validator\Constraints\NotBlank; #[UniqueEntity(fields: ['name', 'user'])] #[ApiResource( - uriTemplate: '/current.{_format}', + uriTemplate: '/tokens/current.{_format}', description: 'A token used to authenticate API requests.', operations: [new Get(openapiContext: ['summary' => 'Get information about the API token that is currently used.'])], normalizationContext: ['groups' => ['token:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], From 915444b5da95f01d7bc6cf43fcb1060dd02308f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 18 Sep 2023 13:02:45 +0200 Subject: [PATCH 31/62] Added documentation about pagination and property filter to API docs. --- config/packages/api_platform.yaml | 5 ++++- docs/api/intro.md | 22 +++++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml index d594f3e8..7d367346 100644 --- a/config/packages/api_platform.yaml +++ b/config/packages/api_platform.yaml @@ -10,4 +10,7 @@ api_platform: # overridden in OpenApiFactoryDecorator access_token: name: Authorization - type: header \ No newline at end of file + type: header + + defaults: + pagination_client_items_per_page: true # Allow clients to override the default items per page \ No newline at end of file diff --git a/docs/api/intro.md b/docs/api/intro.md index b7ec60ae..c308d7fb 100644 --- a/docs/api/intro.md +++ b/docs/api/intro.md @@ -13,6 +13,7 @@ This allows external applications to interact with Part-DB, extend it or integra {: .warning } > This feature is currently in beta. Please report any bugs you find. > The API should not be considered stable yet and could change in future versions, without prior notice. +> 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 be able to access. So currently you should only use the API with trusted users and trusted applications. @@ -68,4 +69,23 @@ API generators which can generate a client library for the API from the schema a Part-DB provides an interactive documentation for the API, which is available under `/api/docs` (so `https://your-part-db.local/api/docs`). You can pass your API token in the form on the top of the page, to authenticate yourself and then you can try out the API directly in the browser. -This is a great way to test the API and see how it works, without having to write any code. \ No newline at end of file +This is a great way to test the API and see how it works, without having to write any code. + +## Pagination + +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/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/categoues/?itemsPerPage=50`). + +See [API Platform docs](https://api-platform.com/docs/core/pagination) for more infos. + +## Property filter + +Sometimes you only want to get a subset of the properties of an entity, for example when you only need the name of a part, but not all the other properties. +You can achieve this using the `properties[]` query parameter with the name of the field you want to get. You can use this parameter multiple times to get multiple fields. +For example if you only want to get the name and the description of a part, you can use `/api/parts/123?properties[]=name&properties[]=description`. +It is also possible to use this filters on list endpoints (get collection), to only get a subset of the properties of all entities in the collection. + +See [API Platform docs](https://api-platform.com/docs/core/filters/#property-filter) for more infos. \ No newline at end of file From d2144a1fed4a18daa7561059b3b37a7d8584fe4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 18 Sep 2023 13:24:33 +0200 Subject: [PATCH 32/62] Added possibility to add a change comment to changes via the API --- docs/api/intro.md | 9 ++- .../AddEditCommentRequestListener.php | 62 +++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 src/EventListener/AddEditCommentRequestListener.php diff --git a/docs/api/intro.md b/docs/api/intro.md index c308d7fb..2c9c1fa1 100644 --- a/docs/api/intro.md +++ b/docs/api/intro.md @@ -88,4 +88,11 @@ You can achieve this using the `properties[]` query parameter with the name of t For example if you only want to get the name and the description of a part, you can use `/api/parts/123?properties[]=name&properties[]=description`. It is also possible to use this filters on list endpoints (get collection), to only get a subset of the properties of all entities in the collection. -See [API Platform docs](https://api-platform.com/docs/core/filters/#property-filter) for more infos. \ No newline at end of file +See [API Platform docs](https://api-platform.com/docs/core/filters/#property-filter) for more infos. + +## Change comment + +Similar to the changes using Part-DB web interface, you can add a change comment to every change you make via the API, which will be +visible in the log of the entity. + +You can pass the text for this via the `_comment` query parameter (beware the proper encoding). For example `/api/parts/123?_comment=This%20is%20a%20change%20comment`. \ No newline at end of file diff --git a/src/EventListener/AddEditCommentRequestListener.php b/src/EventListener/AddEditCommentRequestListener.php new file mode 100644 index 00000000..3f2cc1b5 --- /dev/null +++ b/src/EventListener/AddEditCommentRequestListener.php @@ -0,0 +1,62 @@ +. + */ + +declare(strict_types=1); + + +namespace App\EventListener; + +use App\Services\LogSystem\EventCommentHelper; +use Symfony\Component\EventDispatcher\Attribute\AsEventListener; +use Symfony\Component\HttpKernel\Event\RequestEvent; + +#[AsEventListener()] +class AddEditCommentRequestListener +{ + public function __construct(private readonly EventCommentHelper $helper) + { + + } + + public function __invoke(RequestEvent $event) + { + if (!$event->isMainRequest()) { + return; + } + $request = $event->getRequest(); + + //Do not add comment if the request is a GET request + if ($request->isMethod('GET')) { + return; + } + + //Check if the user tries to access a /api/ endpoint, if not skip + if (!str_contains($request->getPathInfo(), '/api/')) { + return; + } + + //Extract the comment from the query parameter + $comment = $request->query->getString('_comment', ''); + + if ($comment !== '') { + $this->helper->setMessage($comment); + } + } +} \ No newline at end of file From 43c15de55c836556c930c74425f148d660586ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 18 Sep 2023 16:36:08 +0200 Subject: [PATCH 33/62] Added SQLite migrations for API tokens --- migrations/Version20230816213201.php | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/migrations/Version20230816213201.php b/migrations/Version20230816213201.php index d87a676c..66f17d8a 100644 --- a/migrations/Version20230816213201.php +++ b/migrations/Version20230816213201.php @@ -4,30 +4,41 @@ declare(strict_types=1); namespace DoctrineMigrations; +use App\Migration\AbstractMultiPlatformMigration; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; /** * Auto-generated Migration: Please modify to your needs! */ -final class Version20230816213201 extends AbstractMigration +final class Version20230816213201 extends AbstractMultiPlatformMigration { public function getDescription(): string { - return ''; + return 'Add table for API tokens'; } - public function up(Schema $schema): void + public function mySQLUp(Schema $schema): void { - // this up() migration is auto-generated, please modify it to your needs $this->addSql('CREATE TABLE api_tokens (id INT AUTO_INCREMENT NOT NULL, user_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, valid_until DATETIME DEFAULT NULL, token VARCHAR(68) NOT NULL, level SMALLINT NOT NULL, last_time_used DATETIME DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, UNIQUE INDEX UNIQ_2CAD560E5F37A13B (token), INDEX IDX_2CAD560EA76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); $this->addSql('ALTER TABLE api_tokens ADD CONSTRAINT FK_2CAD560EA76ED395 FOREIGN KEY (user_id) REFERENCES `users` (id)'); } - public function down(Schema $schema): void + public function mySQLDown(Schema $schema): void { - // this down() migration is auto-generated, please modify it to your needs $this->addSql('ALTER TABLE api_tokens DROP FOREIGN KEY FK_2CAD560EA76ED395'); $this->addSql('DROP TABLE api_tokens'); } + + public function sqLiteUp(Schema $schema): void + { + $this->addSql('CREATE TABLE api_tokens (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, name VARCHAR(255) NOT NULL, valid_until DATETIME DEFAULT NULL, token VARCHAR(68) NOT NULL, level SMALLINT NOT NULL, last_time_used DATETIME DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_2CAD560EA76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_2CAD560E5F37A13B ON api_tokens (token)'); + $this->addSql('CREATE INDEX IDX_2CAD560EA76ED395 ON api_tokens (user_id)'); + } + + public function sqLiteDown(Schema $schema): void + { + $this->addSql('DROP TABLE api_tokens'); + } } From 59f62d4a4c730282ee69f55ed31cc18fb31406ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 18 Sep 2023 21:31:55 +0200 Subject: [PATCH 34/62] Show lots, order and pricedetails in part response --- src/Entity/Parts/Part.php | 4 ++-- src/Entity/Parts/PartLot.php | 4 ++-- src/Entity/Parts/PartTraits/InstockTrait.php | 2 +- src/Entity/Parts/PartTraits/OrderTrait.php | 2 +- src/Entity/PriceInformations/Orderdetail.php | 4 ++-- src/Entity/PriceInformations/Pricedetail.php | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Entity/Parts/Part.php b/src/Entity/Parts/Part.php index 096c36b0..3f7f945a 100644 --- a/src/Entity/Parts/Part.php +++ b/src/Entity/Parts/Part.php @@ -71,13 +71,13 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; #[ORM\Index(name: 'parts_idx_ipn', columns: ['ipn'])] #[ApiResource( operations: [ - new Get(security: 'is_granted("read", object)'), + new Get(normalizationContext: ['groups' => ['part:read', 'provider_reference:read', 'api:basic:read', 'part_lot:read', 'orderdetail:read', 'pricedetail:read']], security: 'is_granted("read", object)'), new GetCollection(security: 'is_granted("@parts.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:read', 'provider_reference:read', 'api:basic: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'], 'openapi_definition_name' => 'Write'], )] #[ApiFilter(PropertyFilter::class)] diff --git a/src/Entity/Parts/PartLot.php b/src/Entity/Parts/PartLot.php index 7bf85d6b..0f5fe023 100644 --- a/src/Entity/Parts/PartLot.php +++ b/src/Entity/Parts/PartLot.php @@ -66,7 +66,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; new Patch(security: 'is_granted("edit", object)'), new Delete(security: 'is_granted("delete", object)'), ], - normalizationContext: ['groups' => ['part_lot:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + normalizationContext: ['groups' => ['part_lot:read', 'part_lot:read:standalone', 'api:basic:read', 'pricedetail:read'], 'openapi_definition_name' => 'Read'], denormalizationContext: ['groups' => ['part_lot:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], )] #[ApiFilter(PropertyFilter::class)] @@ -133,7 +133,7 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named #[Assert\NotNull] #[ORM\ManyToOne(targetEntity: Part::class, inversedBy: 'partLots')] #[ORM\JoinColumn(name: 'id_part', nullable: false, onDelete: 'CASCADE')] - #[Groups(['part_lot:read', 'part_lot:write'])] + #[Groups(['part_lot:read:standalone', 'part_lot:write'])] protected ?Part $part = null; /** diff --git a/src/Entity/Parts/PartTraits/InstockTrait.php b/src/Entity/Parts/PartTraits/InstockTrait.php index 0a031c55..fa4b0a22 100644 --- a/src/Entity/Parts/PartTraits/InstockTrait.php +++ b/src/Entity/Parts/PartTraits/InstockTrait.php @@ -39,7 +39,7 @@ trait InstockTrait * @var Collection A list of part lots where this part is stored */ #[Assert\Valid] - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\OneToMany(targetEntity: PartLot::class, mappedBy: 'part', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['amount' => 'DESC'])] protected Collection $partLots; diff --git a/src/Entity/Parts/PartTraits/OrderTrait.php b/src/Entity/Parts/PartTraits/OrderTrait.php index 3b2f143c..8b6a379d 100644 --- a/src/Entity/Parts/PartTraits/OrderTrait.php +++ b/src/Entity/Parts/PartTraits/OrderTrait.php @@ -39,7 +39,7 @@ trait OrderTrait * @var Collection The details about how and where you can order this part */ #[Assert\Valid] - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\OneToMany(targetEntity: Orderdetail::class, mappedBy: 'part', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['supplierpartnr' => 'ASC'])] protected Collection $orderdetails; diff --git a/src/Entity/PriceInformations/Orderdetail.php b/src/Entity/PriceInformations/Orderdetail.php index 50b2653b..a7be1c13 100644 --- a/src/Entity/PriceInformations/Orderdetail.php +++ b/src/Entity/PriceInformations/Orderdetail.php @@ -63,7 +63,7 @@ use Symfony\Component\Validator\Constraints as Assert; new Patch(security: 'is_granted("edit", object)'), new Delete(security: 'is_granted("delete", object)'), ], - normalizationContext: ['groups' => ['orderdetail:read', 'api:basic:read', 'pricedetail:read'], 'openapi_definition_name' => 'Read'], + normalizationContext: ['groups' => ['orderdetail:read', 'orderdetail:read:standalone', 'api:basic:read', 'pricedetail:read'], 'openapi_definition_name' => 'Read'], denormalizationContext: ['groups' => ['orderdetail:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], )] #[ApiResource( @@ -115,7 +115,7 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N */ #[Assert\NotNull] #[ORM\ManyToOne(targetEntity: Part::class, inversedBy: 'orderdetails')] - #[Groups(['orderdetail:read', 'orderdetail:write'])] + #[Groups(['orderdetail:read:standalone', 'orderdetail:write'])] #[ORM\JoinColumn(name: 'part_id', nullable: false, onDelete: 'CASCADE')] protected ?Part $part = null; diff --git a/src/Entity/PriceInformations/Pricedetail.php b/src/Entity/PriceInformations/Pricedetail.php index b27fe9cc..2df4fe0f 100644 --- a/src/Entity/PriceInformations/Pricedetail.php +++ b/src/Entity/PriceInformations/Pricedetail.php @@ -61,7 +61,7 @@ use Symfony\Component\Validator\Constraints as Assert; new Patch(security: 'is_granted("edit", object)'), new Delete(security: 'is_granted("delete", object)'), ], - normalizationContext: ['groups' => ['pricedetail:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + normalizationContext: ['groups' => ['pricedetail:read', 'pricedetail:read:standalone', 'api:basic:read'], 'openapi_definition_name' => 'Read'], denormalizationContext: ['groups' => ['pricedetail:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], )] #[ApiFilter(PropertyFilter::class)] @@ -117,7 +117,7 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface #[Assert\NotNull] #[ORM\ManyToOne(targetEntity: Orderdetail::class, inversedBy: 'pricedetails')] #[ORM\JoinColumn(name: 'orderdetails_id', nullable: false, onDelete: 'CASCADE')] - #[Groups(['pricedetail:read', 'pricedetail:write'])] + #[Groups(['pricedetail:read:standalone', 'pricedetail:write'])] protected ?Orderdetail $orderdetail = null; public function __construct() From 8182e8384672669a9f8a35d6c65da723ed04eb81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 18 Sep 2023 21:37:21 +0200 Subject: [PATCH 35/62] Added some helper functions to make it easier to parse Pricedetails --- src/Entity/PriceInformations/Pricedetail.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Entity/PriceInformations/Pricedetail.php b/src/Entity/PriceInformations/Pricedetail.php index 2df4fe0f..3202eb30 100644 --- a/src/Entity/PriceInformations/Pricedetail.php +++ b/src/Entity/PriceInformations/Pricedetail.php @@ -188,6 +188,7 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface * * @return BigDecimal the price as a bcmath string */ + #[Groups(['pricedetail:read'])] public function getPricePerUnit(float|string|BigDecimal $multiplier = 1.0): BigDecimal { $tmp = BigDecimal::of($multiplier); @@ -249,6 +250,17 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface return $this->currency; } + /** + * Returns the ISO code of the currency associated with this price information, or null if no currency is selected. + * Then the global base currency should be assumed. + * @return string|null + */ + #[Groups(['pricedetail:read'])] + public function getCurrencyISOCode(): ?string + { + return $this->currency?->getIsoCode(); + } + /******************************************************************************** * * Setters From 077beb37b1e4917e356258e62999c42d6ef13246 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 18 Sep 2023 21:57:17 +0200 Subject: [PATCH 36/62] Added endpoints for attachments --- src/Entity/Attachments/Attachment.php | 37 ++++++++++++++++++-- src/Entity/PriceInformations/Pricedetail.php | 3 ++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/Entity/Attachments/Attachment.php b/src/Entity/Attachments/Attachment.php index 098300a8..5916b603 100644 --- a/src/Entity/Attachments/Attachment.php +++ b/src/Entity/Attachments/Attachment.php @@ -22,7 +22,13 @@ declare(strict_types=1); namespace App\Entity\Attachments; -use App\Entity\Parts\PartTraits\ProjectTrait; +use ApiPlatform\Metadata\ApiProperty; +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 App\Repository\AttachmentRepository; use App\EntityListeners\AttachmentDeleteListener; use Doctrine\DBAL\Types\Types; @@ -30,6 +36,7 @@ use App\Entity\Base\AbstractNamedDBElement; use App\Validator\Constraints\Selectable; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Annotation\SerializedName; use Symfony\Component\Validator\Constraints as Assert; use function in_array; use InvalidArgumentException; @@ -55,6 +62,18 @@ use LogicException; #[ORM\Index(name: 'attachments_idx_class_name_id', columns: ['class_name', 'id'])] #[ORM\Index(name: 'attachment_name_idx', columns: ['name'])] #[ORM\Index(name: 'attachment_element_idx', columns: ['class_name', 'element_id'])] + +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@attachments.list_attachments")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['attachment:read', 'attachment:read:standalone', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['attachment:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], +)] abstract class Attachment extends AbstractNamedDBElement { /** @@ -102,22 +121,25 @@ abstract class Attachment extends AbstractNamedDBElement * @var string the name of this element */ #[Assert\NotBlank(message: 'validator.attachment.name_not_blank')] - #[Groups(['simple', 'extended', 'full'])] + #[Groups(['simple', 'extended', 'full', 'attachment:read', 'attachment:write'])] protected string $name = ''; /** * ORM mapping is done in subclasses (like PartAttachment). * @phpstan-param T|null $element */ + #[Groups(['attachment:read:standalone', 'attachment:read'])] protected ?AttachmentContainingDBElement $element = null; #[ORM\Column(type: Types::BOOLEAN)] + #[Groups(['attachment:read', 'attachment_write'])] protected bool $show_in_table = false; #[Assert\NotNull(message: 'validator.attachment.must_not_be_null')] #[ORM\ManyToOne(targetEntity: AttachmentType::class, inversedBy: 'attachments_with_type')] #[ORM\JoinColumn(name: 'type_id', nullable: false)] #[Selectable()] + #[Groups(['attachment:read', 'attachment_write'])] protected ?AttachmentType $attachment_type = null; public function __construct() @@ -147,6 +169,7 @@ abstract class Attachment extends AbstractNamedDBElement * @return bool * true if the file extension is a picture extension * * otherwise false */ + #[Groups(['attachment:read'])] public function isPicture(): bool { if ($this->isExternal()) { @@ -171,6 +194,8 @@ abstract class Attachment extends AbstractNamedDBElement * Check if this attachment is a 3D model and therefore can be directly shown to user. * If the attachment is external, false is returned (3D Models must be internal). */ + #[Groups(['attachment:read'])] + #[SerializedName('3d_model')] public function is3DModel(): bool { //We just assume that 3D Models are internally saved, otherwise we get problems loading them. @@ -188,6 +213,7 @@ abstract class Attachment extends AbstractNamedDBElement * * @return bool true, if the file is saved externally */ + #[Groups(['attachment:read'])] public function isExternal(): bool { //When path is empty, this attachment can not be external @@ -207,6 +233,8 @@ abstract class Attachment extends AbstractNamedDBElement * * @return bool true, if the file is secure */ + #[Groups(['attachment:read'])] + #[SerializedName('private')] public function isSecure(): bool { //After the %PLACEHOLDER% comes a slash, so we can check if we have a placeholder via explode @@ -221,6 +249,7 @@ abstract class Attachment extends AbstractNamedDBElement * * @return bool true if the attachment is using a builtin file */ + #[Groups(['attachment:read'])] public function isBuiltIn(): bool { return static::checkIfBuiltin($this->path); @@ -267,6 +296,8 @@ abstract class Attachment extends AbstractNamedDBElement * The URL to the external file, or the path to the built-in file. * Returns null, if the file is not external (and not builtin). */ + #[Groups(['attachment:read'])] + #[SerializedName('url')] public function getURL(): ?string { if (!$this->isExternal() && !$this->isBuiltIn()) { @@ -417,6 +448,8 @@ abstract class Attachment extends AbstractNamedDBElement * * @return Attachment */ + #[Groups(['attachment:write'])] + #[SerializedName('url')] public function setURL(?string $url): self { //Only set if the URL is not empty diff --git a/src/Entity/PriceInformations/Pricedetail.php b/src/Entity/PriceInformations/Pricedetail.php index 3202eb30..c46c3a46 100644 --- a/src/Entity/PriceInformations/Pricedetail.php +++ b/src/Entity/PriceInformations/Pricedetail.php @@ -42,6 +42,7 @@ use DateTime; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Annotation\SerializedName; use Symfony\Component\Validator\Constraints as Assert; /** @@ -189,6 +190,7 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface * @return BigDecimal the price as a bcmath string */ #[Groups(['pricedetail:read'])] + #[SerializedName('price_per_unit')] public function getPricePerUnit(float|string|BigDecimal $multiplier = 1.0): BigDecimal { $tmp = BigDecimal::of($multiplier); @@ -256,6 +258,7 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface * @return string|null */ #[Groups(['pricedetail:read'])] + #[SerializedName('currency_iso_code')] public function getCurrencyISOCode(): ?string { return $this->currency?->getIsoCode(); From 1823bc652898bdcb013db1f9c320eda8ff31972e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 18 Sep 2023 22:16:09 +0200 Subject: [PATCH 37/62] Added url to media file and thumbnail to attachments --- src/Serializer/AttachmentNormalizer.php | 70 +++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/Serializer/AttachmentNormalizer.php diff --git a/src/Serializer/AttachmentNormalizer.php b/src/Serializer/AttachmentNormalizer.php new file mode 100644 index 00000000..7911a9c3 --- /dev/null +++ b/src/Serializer/AttachmentNormalizer.php @@ -0,0 +1,70 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Serializer; + +use App\Entity\Attachments\Attachment; +use App\Services\Attachments\AttachmentURLGenerator; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; + +class AttachmentNormalizer implements NormalizerInterface +{ + public function __construct( + #[Autowire(service: ObjectNormalizer::class)] + private readonly NormalizerInterface $normalizer, + private readonly AttachmentURLGenerator $attachmentURLGenerator, + ) + { + + } + + public function normalize(mixed $object, string $format = null, array $context = []) + { + if (!$object instanceof Attachment) { + throw new \InvalidArgumentException('This normalizer only supports Attachment objects!'); + } + + $data = $this->normalizer->normalize($object, $format, $context); + + $data['media_url'] = $this->attachmentURLGenerator->getViewURL($object); + //Add thumbnail url if the attachment is a picture + $data['thumbnail_url'] = $object->isPicture() ? $this->attachmentURLGenerator->getThumbnailURL($object) : null; + + return $data; + } + + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + { + return $data instanceof Attachment; + } + + public function getSupportedTypes(?string $format): array + { + return [ + //We depend on the context to determine if we should normalize or not + Attachment::class => true, + ]; + } +} \ No newline at end of file From 87e881f8e07a32180f9bcf838a28d4bb4b263cde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 18 Sep 2023 23:38:06 +0200 Subject: [PATCH 38/62] Show attachments and preview image in API response --- config/packages/api_platform.yaml | 3 +++ src/Entity/Attachments/Attachment.php | 3 +-- src/Entity/Parts/Category.php | 4 ++-- src/Entity/Parts/Footprint.php | 6 +++--- src/Entity/Parts/MeasurementUnit.php | 4 ++-- src/Entity/Parts/Part.php | 7 +++++-- src/Entity/UserSystem/User.php | 5 ++--- 7 files changed, 18 insertions(+), 14 deletions(-) diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml index 7d367346..e304a134 100644 --- a/config/packages/api_platform.yaml +++ b/config/packages/api_platform.yaml @@ -5,6 +5,9 @@ api_platform: version: '0.1.0' +# eager_loading: +# max_joins: 100 + swagger: api_keys: # overridden in OpenApiFactoryDecorator diff --git a/src/Entity/Attachments/Attachment.php b/src/Entity/Attachments/Attachment.php index 5916b603..84d0260d 100644 --- a/src/Entity/Attachments/Attachment.php +++ b/src/Entity/Attachments/Attachment.php @@ -62,7 +62,6 @@ use LogicException; #[ORM\Index(name: 'attachments_idx_class_name_id', columns: ['class_name', 'id'])] #[ORM\Index(name: 'attachment_name_idx', columns: ['name'])] #[ORM\Index(name: 'attachment_element_idx', columns: ['class_name', 'element_id'])] - #[ApiResource( operations: [ new Get(security: 'is_granted("read", object)'), @@ -128,7 +127,7 @@ abstract class Attachment extends AbstractNamedDBElement * ORM mapping is done in subclasses (like PartAttachment). * @phpstan-param T|null $element */ - #[Groups(['attachment:read:standalone', 'attachment:read'])] + #[Groups(['attachment:read:standalone', 'attachment:write'])] protected ?AttachmentContainingDBElement $element = null; #[ORM\Column(type: Types::BOOLEAN)] diff --git a/src/Entity/Parts/Category.php b/src/Entity/Parts/Category.php index b1714fe9..11731ab6 100644 --- a/src/Entity/Parts/Category.php +++ b/src/Entity/Parts/Category.php @@ -152,14 +152,14 @@ class Category extends AbstractPartsContainingDBElement * @var Collection */ #[Assert\Valid] - #[Groups(['full', 'category:read'])] + #[Groups(['full', 'category:read', 'category:write'])] #[ORM\OneToMany(targetEntity: CategoryAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['name' => 'ASC'])] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: CategoryAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] - #[Groups(['category:read'])] + #[Groups(['category:read', 'category:write'])] protected ?Attachment $master_picture_attachment = null; /** @var Collection diff --git a/src/Entity/Parts/Footprint.php b/src/Entity/Parts/Footprint.php index c7148deb..edeb31cc 100644 --- a/src/Entity/Parts/Footprint.php +++ b/src/Entity/Parts/Footprint.php @@ -98,12 +98,12 @@ class Footprint extends AbstractPartsContainingDBElement #[Assert\Valid] #[ORM\OneToMany(targetEntity: FootprintAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['name' => 'ASC'])] - #[Groups(['footprint:read'])] + #[Groups(['footprint:read', 'footprint:write'])] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: FootprintAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] - #[Groups(['footprint:read'])] + #[Groups(['footprint:read', 'footprint:write'])] protected ?Attachment $master_picture_attachment = null; /** @@ -111,7 +111,7 @@ class Footprint extends AbstractPartsContainingDBElement */ #[ORM\ManyToOne(targetEntity: FootprintAttachment::class)] #[ORM\JoinColumn(name: 'id_footprint_3d')] - #[Groups(['footprint:read'])] + #[Groups(['footprint:read', 'footprint:write'])] protected ?FootprintAttachment $footprint_3d = null; /** @var Collection diff --git a/src/Entity/Parts/MeasurementUnit.php b/src/Entity/Parts/MeasurementUnit.php index 18c6e2ed..d9437469 100644 --- a/src/Entity/Parts/MeasurementUnit.php +++ b/src/Entity/Parts/MeasurementUnit.php @@ -128,12 +128,12 @@ class MeasurementUnit extends AbstractPartsContainingDBElement #[Assert\Valid] #[ORM\OneToMany(targetEntity: MeasurementUnitAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['name' => 'ASC'])] - #[Groups(['measurement_unit:read'])] + #[Groups(['measurement_unit:read', 'measurement_unit:write'])] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: MeasurementUnitAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] - #[Groups(['measurement_unit:read'])] + #[Groups(['measurement_unit:read', 'measurement_unit:write'])] protected ?Attachment $master_picture_attachment = null; /** @var Collection diff --git a/src/Entity/Parts/Part.php b/src/Entity/Parts/Part.php index 3f7f945a..1f7ace26 100644 --- a/src/Entity/Parts/Part.php +++ b/src/Entity/Parts/Part.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace App\Entity\Parts; use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Delete; use ApiPlatform\Metadata\Get; @@ -71,7 +72,8 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; #[ORM\Index(name: 'parts_idx_ipn', columns: ['ipn'])] #[ApiResource( operations: [ - new Get(normalizationContext: ['groups' => ['part:read', 'provider_reference:read', 'api:basic:read', 'part_lot:read', 'orderdetail:read', 'pricedetail:read']], security: 'is_granted("read", object)'), + new Get(normalizationContext: ['groups' => ['part:read', 'provider_reference:read', 'api:basic:read', 'part_lot:read', + 'orderdetail:read', 'pricedetail:read', 'attachment:read']], security: 'is_granted("read", object)'), new GetCollection(security: 'is_granted("@parts.read")'), new Post(securityPostDenormalize: 'is_granted("create", object)'), new Patch(security: 'is_granted("edit", object)'), @@ -115,7 +117,7 @@ class Part extends AttachmentContainingDBElement * @var Collection */ #[Assert\Valid] - #[Groups(['full'])] + #[Groups(['full', 'part:read', 'part:write'])] #[ORM\OneToMany(targetEntity: PartAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['name' => 'ASC'])] protected Collection $attachments; @@ -126,6 +128,7 @@ class Part extends AttachmentContainingDBElement #[Assert\Expression('value == null or value.isPicture()', message: 'part.master_attachment.must_be_picture')] #[ORM\ManyToOne(targetEntity: PartAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] + #[Groups(['part:read', 'part:write'])] protected ?Attachment $master_picture_attachment = null; public function __construct() diff --git a/src/Entity/UserSystem/User.php b/src/Entity/UserSystem/User.php index 31a8e6e3..6ce36d90 100644 --- a/src/Entity/UserSystem/User.php +++ b/src/Entity/UserSystem/User.php @@ -253,13 +253,12 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe */ #[ORM\OneToMany(mappedBy: 'element', targetEntity: UserAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['name' => 'ASC'])] - #[Groups(['user:read'])] - #[ApiProperty(readableLink: false, writableLink: false)] + #[Groups(['user:read', 'user:write'])] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: UserAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] - #[Groups(['user:read'])] + #[Groups(['user:read', 'usser:write'])] protected ?Attachment $master_picture_attachment = null; /** @var \DateTimeInterface|null The time when the backup codes were generated From 34059ad99d3c53cf62ad5681c0aa176fc93f403f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 18 Sep 2023 23:46:42 +0200 Subject: [PATCH 39/62] Added API endpoints for attachment types --- src/Entity/Attachments/AttachmentType.php | 45 +++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/Entity/Attachments/AttachmentType.php b/src/Entity/Attachments/AttachmentType.php index dc8de0a0..23c3d322 100644 --- a/src/Entity/Attachments/AttachmentType.php +++ b/src/Entity/Attachments/AttachmentType.php @@ -22,6 +22,17 @@ declare(strict_types=1); namespace App\Entity\Attachments; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\Entity\Parts\Footprint; use App\Repository\StructuralDBElementRepository; use Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractStructuralDBElement; @@ -30,6 +41,7 @@ use App\Validator\Constraints\ValidFileFilter; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; /** @@ -41,6 +53,29 @@ use Symfony\Component\Validator\Constraints as Assert; #[ORM\Table(name: '`attachment_types`')] #[ORM\Index(name: 'attachment_types_idx_name', columns: ['name'])] #[ORM\Index(name: 'attachment_types_idx_parent_name', columns: ['parent_id', 'name'])] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@attachment_types.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['attachment_type:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['attachment_type:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiResource( + uriTemplate: '/attachment_types/{id}/children.{_format}', + operations: [ + new GetCollection(openapiContext: ['summary' => 'Retrieves the children elements of an attachment type.'], + security: 'is_granted("@attachment_types.read")') + ], + uriVariables: [ + 'id' => new Link(fromProperty: 'children', fromClass: AttachmentType::class) + ], + normalizationContext: ['groups' => ['footprint:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] +)] +#[ApiFilter(PropertyFilter::class)] class AttachmentType extends AbstractStructuralDBElement { #[ORM\OneToMany(targetEntity: AttachmentType::class, mappedBy: 'parent', cascade: ['persist'])] @@ -49,10 +84,18 @@ class AttachmentType extends AbstractStructuralDBElement #[ORM\ManyToOne(targetEntity: AttachmentType::class, inversedBy: 'children')] #[ORM\JoinColumn(name: 'parent_id')] + #[Groups(['attachment_type:read', 'attachment_type:write'])] + #[ApiProperty(readableLink: false, writableLink: false)] protected ?AbstractStructuralDBElement $parent = null; + /** + * @var string A comma separated list of file types, which are allowed for attachment files. + * Must be in the format of accept attribute + * (See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers). + */ #[ORM\Column(type: Types::TEXT)] #[ValidFileFilter] + #[Groups(['attachment_type:read', 'attachment_type:write'])] protected string $filetype_filter = ''; /** @@ -61,10 +104,12 @@ class AttachmentType extends AbstractStructuralDBElement #[Assert\Valid] #[ORM\OneToMany(targetEntity: AttachmentTypeAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['name' => 'ASC'])] + #[Groups(['attachment_type:read', 'attachment_type:write'])] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: AttachmentTypeAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] + #[Groups(['attachment_type:read', 'attachment_type:write'])] protected ?Attachment $master_picture_attachment = null; /** @var Collection From 8a1379982948532654c1010089ece16f2d2e0f15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 18 Sep 2023 23:57:28 +0200 Subject: [PATCH 40/62] (Hopefully) fix static analysis github action --- .github/workflows/static_analysis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 41ff1a56..8d2cf609 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -48,9 +48,10 @@ jobs: - name: Check doctrine mapping run: ./bin/console doctrine:schema:validate --skip-sync -vvv --no-interaction - + + # Use the -d option to raise the max nesting level - name: Generate dev container - run: ./bin/console cache:clear --env dev + run: php -d xdebug.max_nesting_level=1000 ./bin/console cache:clear --env dev - name: Run PHPstan run: composer phpstan From 78a6262665e6f3dda3addd2600a5947e691f0222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Tue, 19 Sep 2023 00:00:25 +0200 Subject: [PATCH 41/62] Fixed PHPstan issues --- src/Entity/Base/AbstractPartsContainingDBElement.php | 3 --- src/Entity/Parts/MeasurementUnit.php | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Entity/Base/AbstractPartsContainingDBElement.php b/src/Entity/Base/AbstractPartsContainingDBElement.php index 3ea1c216..5d25283e 100644 --- a/src/Entity/Base/AbstractPartsContainingDBElement.php +++ b/src/Entity/Base/AbstractPartsContainingDBElement.php @@ -40,9 +40,6 @@ use Symfony\Component\Serializer\Annotation\Groups; #[ORM\MappedSuperclass(repositoryClass: AbstractPartsContainingRepository::class)] abstract class AbstractPartsContainingDBElement extends AbstractStructuralDBElement { - /** - * @var Collection|ArrayCollection The parameters of this element - */ #[Groups(['full'])] protected Collection $parameters; diff --git a/src/Entity/Parts/MeasurementUnit.php b/src/Entity/Parts/MeasurementUnit.php index d9437469..2b7130c9 100644 --- a/src/Entity/Parts/MeasurementUnit.php +++ b/src/Entity/Parts/MeasurementUnit.php @@ -118,7 +118,7 @@ class MeasurementUnit extends AbstractPartsContainingDBElement #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] #[ORM\JoinColumn(name: 'parent_id')] - #[Groups('measurement_unit:read', 'measurement_unit:write')] + #[Groups(['measurement_unit:read', 'measurement_unit:write'])] #[ApiProperty(readableLink: false, writableLink: false)] protected ?AbstractStructuralDBElement $parent = null; From f285061a762947e7566968c9fd84b354f6f6bf17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Tue, 19 Sep 2023 11:44:22 +0200 Subject: [PATCH 42/62] Allow to view and edit parameters of elements --- docs/api/intro.md | 2 ++ src/Entity/Attachments/AttachmentType.php | 1 + src/Entity/Parameters/AbstractParameter.php | 34 +++++++++++++++++---- src/Entity/Parts/Category.php | 2 +- src/Entity/Parts/Footprint.php | 2 +- src/Entity/Parts/MeasurementUnit.php | 2 +- src/Entity/Parts/Part.php | 6 ++-- src/Entity/Parts/StorageLocation.php | 1 + 8 files changed, 39 insertions(+), 11 deletions(-) diff --git a/docs/api/intro.md b/docs/api/intro.md index 2c9c1fa1..44b0db19 100644 --- a/docs/api/intro.md +++ b/docs/api/intro.md @@ -58,6 +58,8 @@ The API supports different formats for the request and response data, which you You should use [JSON-LD](https://json-ld.org/) as format, which is basically JSON with some additional metadata, which allows to describe the data in a more structured way and also allows to link between different entities. You can achieve this by setting `Accept: application/ld+json` header to the API requests. +To get plain JSON without any metadata or links, use the `Accept: application/json` header. + ## OpenAPI schema Part-DB provides a [OpenAPI](https://swagger.io/specification/) (formally Swagger) schema for the API under `/api/docs.json` (so `https://your-part-db.local/api/docs.json`). diff --git a/src/Entity/Attachments/AttachmentType.php b/src/Entity/Attachments/AttachmentType.php index 23c3d322..c6791d90 100644 --- a/src/Entity/Attachments/AttachmentType.php +++ b/src/Entity/Attachments/AttachmentType.php @@ -117,6 +117,7 @@ class AttachmentType extends AbstractStructuralDBElement #[Assert\Valid] #[ORM\OneToMany(targetEntity: AttachmentTypeParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[Groups(['attachment_type:read', 'attachment_type:write'])] protected Collection $parameters; /** diff --git a/src/Entity/Parameters/AbstractParameter.php b/src/Entity/Parameters/AbstractParameter.php index a3cef469..7e90a546 100644 --- a/src/Entity/Parameters/AbstractParameter.php +++ b/src/Entity/Parameters/AbstractParameter.php @@ -41,6 +41,12 @@ declare(strict_types=1); namespace App\Entity\Parameters; +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 App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\ParameterRepository; use Doctrine\DBAL\Types\Types; @@ -49,7 +55,9 @@ use App\Entity\Base\AbstractNamedDBElement; use Doctrine\ORM\Mapping as ORM; use InvalidArgumentException; use LogicException; +use PHPUnit\Framework\Attributes\Group; use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Annotation\SerializedName; use Symfony\Component\Validator\Constraints as Assert; use function sprintf; @@ -65,6 +73,17 @@ use function sprintf; #[ORM\Index(name: 'parameter_name_idx', columns: ['name'])] #[ORM\Index(name: 'parameter_group_idx', columns: ['param_group'])] #[ORM\Index(name: 'parameter_type_element_idx', columns: ['type', 'element_id'])] +#[ApiResource( + shortName: 'Parameter', + operations: [ + new Get(security: 'is_granted("read", object)'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['parameter:read', 'parameter:read:standalone', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['parameter:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], +)] abstract class AbstractParameter extends AbstractNamedDBElement { /** @@ -76,7 +95,7 @@ abstract class AbstractParameter extends AbstractNamedDBElement * @var string The mathematical symbol for this specification. Can be rendered pretty later. Should be short */ #[Assert\Length(max: 20)] - #[Groups(['full'])] + #[Groups(['full', 'parameter:read', 'parameter:write'])] #[ORM\Column(type: Types::STRING)] protected string $symbol = ''; @@ -86,7 +105,7 @@ abstract class AbstractParameter extends AbstractNamedDBElement #[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'])] + #[Groups(['full', 'parameter:read', 'parameter_write'])] #[ORM\Column(type: Types::FLOAT, nullable: true)] protected ?float $value_min = null; @@ -94,7 +113,7 @@ abstract class AbstractParameter extends AbstractNamedDBElement * @var float|null the typical value of this property */ #[Assert\Type([null, 'float'])] - #[Groups(['full'])] + #[Groups(['full', 'parameter:read', 'parameter:write'])] #[ORM\Column(type: Types::FLOAT, nullable: true)] protected ?float $value_typical = null; @@ -110,21 +129,21 @@ abstract class AbstractParameter extends AbstractNamedDBElement /** * @var string The unit in which the value values are given (e.g. V) */ - #[Groups(['full'])] + #[Groups(['full', 'parameter:read', 'parameter:write'])] #[ORM\Column(type: Types::STRING)] protected string $unit = ''; /** * @var string a text value for the given property */ - #[Groups(['full'])] + #[Groups(['full', 'parameter:read', 'parameter:write'])] #[ORM\Column(type: Types::STRING)] protected string $value_text = ''; /** * @var string the group this parameter belongs to */ - #[Groups(['full'])] + #[Groups(['full', 'parameter:read', 'parameter:write'])] #[ORM\Column(type: Types::STRING, name: 'param_group')] protected string $group = ''; @@ -133,6 +152,7 @@ abstract class AbstractParameter extends AbstractNamedDBElement * * @var AbstractDBElement|null the element to which this parameter belongs to */ + #[Groups(['parameter:read:standalone', 'parameter:write'])] protected ?AbstractDBElement $element = null; public function __construct() @@ -162,6 +182,8 @@ abstract class AbstractParameter extends AbstractNamedDBElement * Return a formatted string version of the values of the string. * Based on the set values it can return something like this: 34 V (12 V ... 50 V) [Text]. */ + #[Groups('parameter:read', 'full')] + #[SerializedName('formatted')] public function getFormattedValue(): string { //If we just only have text value, return early diff --git a/src/Entity/Parts/Category.php b/src/Entity/Parts/Category.php index 11731ab6..b32cbbf4 100644 --- a/src/Entity/Parts/Category.php +++ b/src/Entity/Parts/Category.php @@ -165,7 +165,7 @@ class Category extends AbstractPartsContainingDBElement /** @var Collection */ #[Assert\Valid] - #[Groups(['full', 'category:read'])] + #[Groups(['full', 'category:read', 'category:write'])] #[ORM\OneToMany(targetEntity: CategoryParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] protected Collection $parameters; diff --git a/src/Entity/Parts/Footprint.php b/src/Entity/Parts/Footprint.php index edeb31cc..8dde5931 100644 --- a/src/Entity/Parts/Footprint.php +++ b/src/Entity/Parts/Footprint.php @@ -119,7 +119,7 @@ class Footprint extends AbstractPartsContainingDBElement #[Assert\Valid] #[ORM\OneToMany(targetEntity: FootprintParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] - #[Groups(['footprint:read'])] + #[Groups(['footprint:read', 'footprint:write'])] protected Collection $parameters; /**************************************** diff --git a/src/Entity/Parts/MeasurementUnit.php b/src/Entity/Parts/MeasurementUnit.php index 2b7130c9..7d9e76a4 100644 --- a/src/Entity/Parts/MeasurementUnit.php +++ b/src/Entity/Parts/MeasurementUnit.php @@ -141,7 +141,7 @@ class MeasurementUnit extends AbstractPartsContainingDBElement #[Assert\Valid] #[ORM\OneToMany(targetEntity: MeasurementUnitParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] - #[Groups(['measurement_unit:read'])] + #[Groups(['measurement_unit:read', 'measurement_unit:write'])] protected Collection $parameters; /** diff --git a/src/Entity/Parts/Part.php b/src/Entity/Parts/Part.php index 1f7ace26..56e6638b 100644 --- a/src/Entity/Parts/Part.php +++ b/src/Entity/Parts/Part.php @@ -73,7 +73,9 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; #[ApiResource( operations: [ new Get(normalizationContext: ['groups' => ['part:read', 'provider_reference:read', 'api:basic:read', 'part_lot:read', - 'orderdetail:read', 'pricedetail:read', 'attachment:read']], security: 'is_granted("read", object)'), + 'orderdetail:read', 'pricedetail:read', 'parameter:read', 'attachment:read'], + 'openapi_definition_name' => 'Read', + ], security: 'is_granted("read", object)'), new GetCollection(security: 'is_granted("@parts.read")'), new Post(securityPostDenormalize: 'is_granted("create", object)'), new Patch(security: 'is_granted("edit", object)'), @@ -97,7 +99,7 @@ class Part extends AttachmentContainingDBElement /** @var Collection */ #[Assert\Valid] - #[Groups(['full'])] + #[Groups(['full', 'part:read', 'part:write'])] #[ORM\OneToMany(targetEntity: PartParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] protected Collection $parameters; diff --git a/src/Entity/Parts/StorageLocation.php b/src/Entity/Parts/StorageLocation.php index c21d3f28..4a11427c 100644 --- a/src/Entity/Parts/StorageLocation.php +++ b/src/Entity/Parts/StorageLocation.php @@ -105,6 +105,7 @@ class StorageLocation extends AbstractPartsContainingDBElement #[Assert\Valid] #[ORM\OneToMany(targetEntity: StorageLocationParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[Groups(['location:read', 'location:write'])] protected Collection $parameters; /** From ccb94c8a139c5e76ae85221f484d3ebdd90b32b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Tue, 19 Sep 2023 23:52:11 +0200 Subject: [PATCH 43/62] Fixed problem that all properties in snake_case style were considered readOnly by API Platform --- src/Entity/Base/TimestampTrait.php | 3 + .../SnakeCasePropertyAccessExtractor.php | 71 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 src/Services/SnakeCasePropertyAccessExtractor.php diff --git a/src/Entity/Base/TimestampTrait.php b/src/Entity/Base/TimestampTrait.php index 93e58cb7..e6fac441 100644 --- a/src/Entity/Base/TimestampTrait.php +++ b/src/Entity/Base/TimestampTrait.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Base; +use ApiPlatform\Metadata\ApiProperty; use Doctrine\DBAL\Types\Types; use DateTime; use Doctrine\ORM\Mapping as ORM; @@ -36,6 +37,7 @@ trait TimestampTrait * @var \DateTimeInterface|null the date when this element was modified the last time */ #[Groups(['extended', 'full'])] + #[ApiProperty(writable: false)] #[ORM\Column(name: 'last_modified', type: Types::DATETIME_MUTABLE, options: ['default' => 'CURRENT_TIMESTAMP'])] protected ?\DateTimeInterface $lastModified = null; @@ -43,6 +45,7 @@ trait TimestampTrait * @var \DateTimeInterface|null the date when this element was created */ #[Groups(['extended', 'full'])] + #[ApiProperty(writable: false)] #[ORM\Column(name: 'datetime_added', type: Types::DATETIME_MUTABLE, options: ['default' => 'CURRENT_TIMESTAMP'])] protected ?\DateTimeInterface $addedDate = null; diff --git a/src/Services/SnakeCasePropertyAccessExtractor.php b/src/Services/SnakeCasePropertyAccessExtractor.php new file mode 100644 index 00000000..bfe82c63 --- /dev/null +++ b/src/Services/SnakeCasePropertyAccessExtractor.php @@ -0,0 +1,71 @@ +. + */ + +declare(strict_types=1); + +namespace App\Services; + +use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; +use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; + +/** + * Workaround for using snake_case properties with ReflectionExtractor until this PR is merged: + * https://github.com/symfony/symfony/pull/51697 + */ +#[AsTaggedItem('property_info.access_extractor', priority: 0)] +class SnakeCasePropertyAccessExtractor implements PropertyAccessExtractorInterface +{ + + public function __construct(#[Autowire(service: 'property_info.reflection_extractor')] + private readonly PropertyAccessExtractorInterface $reflectionExtractor) + { + //$this->reflectionExtractor = new ReflectionExtractor(); + } + + public function isReadable(string $class, string $property, array $context = []) + { + //Null means skip this extractor + return null; + } + + /** + * Camelizes a given string. + */ + private function camelize(string $string): string + { + return str_replace(' ', '', ucwords(str_replace('_', ' ', $string))); + } + + + public function isWritable(string $class, string $property, array $context = []) + { + //Check writeablity using a camelized property name + $isWriteable = $this->reflectionExtractor->isWritable($class, $this->camelize($property), $context); + //If we found a writeable property that way, return true + if ($isWriteable === true) { + return true; + } + + //Otherwise skip this extractor + return null; + } +} \ No newline at end of file From c49aff5cfcd420daa7275dbd228c7aa1b0edd1cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 1 Oct 2023 15:57:18 +0200 Subject: [PATCH 44/62] Removed GraphQL endpoint --- composer.json | 3 +- composer.lock | 569 ++++++++++++++++++++++---------------------------- 2 files changed, 255 insertions(+), 317 deletions(-) diff --git a/composer.json b/composer.json index 4711a111..84572c0b 100644 --- a/composer.json +++ b/composer.json @@ -86,8 +86,7 @@ "twig/intl-extra": "^3.0", "twig/markdown-extra": "^3.0", "web-auth/webauthn-symfony-bundle": "^4.0.0", - "webmozart/assert": "^1.4", - "webonyx/graphql-php": "^15.6" + "webmozart/assert": "^1.4" }, "require-dev": { "dama/doctrine-test-bundle": "^7.0", diff --git a/composer.lock b/composer.lock index 89715776..dac0b4bd 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": "cab75954c0c9b5570e36456a8a2bc9ed", + "content-hash": "9b3518d81d4db4a9d053779667af53a3", "packages": [ { "name": "api-platform/core", @@ -933,16 +933,16 @@ }, { "name": "doctrine/dbal", - "version": "3.6.6", + "version": "3.7.0", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "63646ffd71d1676d2f747f871be31b7e921c7864" + "reference": "00d03067f07482f025d41ab55e4ba0db5eca2cdf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/63646ffd71d1676d2f747f871be31b7e921c7864", - "reference": "63646ffd71d1676d2f747f871be31b7e921c7864", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/00d03067f07482f025d41ab55e4ba0db5eca2cdf", + "reference": "00d03067f07482f025d41ab55e4ba0db5eca2cdf", "shasum": "" }, "require": { @@ -958,9 +958,9 @@ "doctrine/coding-standard": "12.0.0", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2023.1", - "phpstan/phpstan": "1.10.29", + "phpstan/phpstan": "1.10.35", "phpstan/phpstan-strict-rules": "^1.5", - "phpunit/phpunit": "9.6.9", + "phpunit/phpunit": "9.6.13", "psalm/plugin-phpunit": "0.18.4", "slevomat/coding-standard": "8.13.1", "squizlabs/php_codesniffer": "3.7.2", @@ -1026,7 +1026,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.6.6" + "source": "https://github.com/doctrine/dbal/tree/3.7.0" }, "funding": [ { @@ -1042,20 +1042,20 @@ "type": "tidelift" } ], - "time": "2023-08-17T05:38:17+00:00" + "time": "2023-09-26T20:56:55+00:00" }, { "name": "doctrine/deprecations", - "version": "v1.1.1", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3" + "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/612a3ee5ab0d5dd97b7cf3874a6efe24325efac3", - "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/4f2d4f2836e7ec4e7a8625e75c6aa916004db931", + "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931", "shasum": "" }, "require": { @@ -1087,9 +1087,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/v1.1.1" + "source": "https://github.com/doctrine/deprecations/tree/1.1.2" }, - "time": "2023-06-03T09:27:29+00:00" + "time": "2023-09-27T20:04:15+00:00" }, { "name": "doctrine/doctrine-bundle", @@ -2038,7 +2038,7 @@ "issues": "https://github.com/dompdf/dompdf/issues", "source": "https://github.com/dompdf/dompdf/tree/master" }, - "time": "2023-09-12T15:54:38+00:00" + "time": "2023-09-24T14:09:26+00:00" }, { "name": "egulias/email-validator", @@ -2442,16 +2442,16 @@ }, { "name": "gregwar/captcha", - "version": "v1.2.0", + "version": "v1.2.1", "source": { "type": "git", "url": "https://github.com/Gregwar/Captcha.git", - "reference": "6e5b61b66ac89885b505153f4ef9a74ffa5b3074" + "reference": "229d3cdfe33d6f1349e0aec94a26e9205a6db08e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Gregwar/Captcha/zipball/6e5b61b66ac89885b505153f4ef9a74ffa5b3074", - "reference": "6e5b61b66ac89885b505153f4ef9a74ffa5b3074", + "url": "https://api.github.com/repos/Gregwar/Captcha/zipball/229d3cdfe33d6f1349e0aec94a26e9205a6db08e", + "reference": "229d3cdfe33d6f1349e0aec94a26e9205a6db08e", "shasum": "" }, "require": { @@ -2493,9 +2493,9 @@ ], "support": { "issues": "https://github.com/Gregwar/Captcha/issues", - "source": "https://github.com/Gregwar/Captcha/tree/v1.2.0" + "source": "https://github.com/Gregwar/Captcha/tree/v1.2.1" }, - "time": "2023-03-24T22:12:41+00:00" + "time": "2023-09-26T13:45:37+00:00" }, { "name": "gregwar/captcha-bundle", @@ -3808,22 +3808,22 @@ }, { "name": "liip/imagine-bundle", - "version": "2.11.0", + "version": "2.12.0", "source": { "type": "git", "url": "https://github.com/liip/LiipImagineBundle.git", - "reference": "2e943e6be4309ec90fcff90227053898203c1c8e" + "reference": "1f5d77626f7104bc98c17194ed9f2153b91564df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/liip/LiipImagineBundle/zipball/2e943e6be4309ec90fcff90227053898203c1c8e", - "reference": "2e943e6be4309ec90fcff90227053898203c1c8e", + "url": "https://api.github.com/repos/liip/LiipImagineBundle/zipball/1f5d77626f7104bc98c17194ed9f2153b91564df", + "reference": "1f5d77626f7104bc98c17194ed9f2153b91564df", "shasum": "" }, "require": { "ext-mbstring": "*", "imagine/imagine": "^1.3.2", - "php": "^7.1|^8.0", + "php": "^7.2|^8.0", "symfony/filesystem": "^3.4|^4.4|^5.3|^6.0", "symfony/finder": "^3.4|^4.4|^5.3|^6.0", "symfony/framework-bundle": "^3.4.23|^4.4|^5.3|^6.0", @@ -3840,7 +3840,7 @@ "enqueue/enqueue-bundle": "^0.9|^0.10", "ext-gd": "*", "league/flysystem": "^1.0|^2.0|^3.0", - "phpstan/phpstan": "^0.12.64", + "phpstan/phpstan": "^1.10.0", "psr/cache": "^1.0|^2.0|^3.0", "psr/log": "^1.0", "symfony/browser-kit": "^3.4|^4.4|^5.3|^6.0", @@ -3905,9 +3905,9 @@ ], "support": { "issues": "https://github.com/liip/LiipImagineBundle/issues", - "source": "https://github.com/liip/LiipImagineBundle/tree/2.11.0" + "source": "https://github.com/liip/LiipImagineBundle/tree/2.12.0" }, - "time": "2023-05-16T06:13:14+00:00" + "time": "2023-09-23T14:59:30+00:00" }, { "name": "lorenzo/pinky", @@ -5708,16 +5708,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.24.0", + "version": "1.24.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "3510b0a6274cc42f7219367cb3abfc123ffa09d6" + "reference": "bcad8d995980440892759db0c32acae7c8e79442" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/3510b0a6274cc42f7219367cb3abfc123ffa09d6", - "reference": "3510b0a6274cc42f7219367cb3abfc123ffa09d6", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/bcad8d995980440892759db0c32acae7c8e79442", + "reference": "bcad8d995980440892759db0c32acae7c8e79442", "shasum": "" }, "require": { @@ -5749,9 +5749,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.2" }, - "time": "2023-09-07T20:46:32+00:00" + "time": "2023-09-26T12:28:12+00:00" }, { "name": "psr/cache", @@ -5955,16 +5955,16 @@ }, { "name": "psr/http-client", - "version": "1.0.2", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/php-fig/http-client.git", - "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31" + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/0955afe48220520692d2d09f7ab7e0f93ffd6a31", - "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", "shasum": "" }, "require": { @@ -6001,9 +6001,9 @@ "psr-18" ], "support": { - "source": "https://github.com/php-fig/http-client/tree/1.0.2" + "source": "https://github.com/php-fig/http-client" }, - "time": "2023-04-10T20:12:12+00:00" + "time": "2023-09-23T14:17:50+00:00" }, { "name": "psr/http-factory", @@ -7360,16 +7360,16 @@ }, { "name": "symfony/cache", - "version": "v6.3.4", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "e60d00b4f633efa4c1ef54e77c12762d9073e7b3" + "reference": "6c1a3ea078c4d88ee892530945df63a87981b2da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/e60d00b4f633efa4c1ef54e77c12762d9073e7b3", - "reference": "e60d00b4f633efa4c1ef54e77c12762d9073e7b3", + "url": "https://api.github.com/repos/symfony/cache/zipball/6c1a3ea078c4d88ee892530945df63a87981b2da", + "reference": "6c1a3ea078c4d88ee892530945df63a87981b2da", "shasum": "" }, "require": { @@ -7436,7 +7436,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v6.3.4" + "source": "https://github.com/symfony/cache/tree/v6.3.5" }, "funding": [ { @@ -7452,7 +7452,7 @@ "type": "tidelift" } ], - "time": "2023-08-05T09:10:27+00:00" + "time": "2023-09-26T15:48:55+00:00" }, { "name": "symfony/cache-contracts", @@ -7835,16 +7835,16 @@ }, { "name": "symfony/dependency-injection", - "version": "v6.3.4", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "68a5a9570806a087982f383f6109c5e925892a49" + "reference": "2ed62b3bf98346e1f45529a7b6be2196739bb993" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/68a5a9570806a087982f383f6109c5e925892a49", - "reference": "68a5a9570806a087982f383f6109c5e925892a49", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/2ed62b3bf98346e1f45529a7b6be2196739bb993", + "reference": "2ed62b3bf98346e1f45529a7b6be2196739bb993", "shasum": "" }, "require": { @@ -7896,7 +7896,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/v6.3.4" + "source": "https://github.com/symfony/dependency-injection/tree/v6.3.5" }, "funding": [ { @@ -7912,7 +7912,7 @@ "type": "tidelift" } ], - "time": "2023-08-16T17:55:17+00:00" + "time": "2023-09-25T16:46:40+00:00" }, { "name": "symfony/deprecation-contracts", @@ -7983,16 +7983,16 @@ }, { "name": "symfony/doctrine-bridge", - "version": "v6.3.4", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-bridge.git", - "reference": "589eeeb93669739ec1d8bd4593e4972d94e0981d" + "reference": "9977eb1adf999ceded213e88c1ac6dff7a1a0306" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/589eeeb93669739ec1d8bd4593e4972d94e0981d", - "reference": "589eeeb93669739ec1d8bd4593e4972d94e0981d", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/9977eb1adf999ceded213e88c1ac6dff7a1a0306", + "reference": "9977eb1adf999ceded213e88c1ac6dff7a1a0306", "shasum": "" }, "require": { @@ -8073,7 +8073,7 @@ "description": "Provides integration for Doctrine with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/doctrine-bridge/tree/v6.3.4" + "source": "https://github.com/symfony/doctrine-bridge/tree/v6.3.5" }, "funding": [ { @@ -8089,7 +8089,7 @@ "type": "tidelift" } ], - "time": "2023-08-08T10:40:25+00:00" + "time": "2023-09-29T16:16:03+00:00" }, { "name": "symfony/dotenv", @@ -8167,16 +8167,16 @@ }, { "name": "symfony/error-handler", - "version": "v6.3.2", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "85fd65ed295c4078367c784e8a5a6cee30348b7a" + "reference": "1f69476b64fb47105c06beef757766c376b548c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/85fd65ed295c4078367c784e8a5a6cee30348b7a", - "reference": "85fd65ed295c4078367c784e8a5a6cee30348b7a", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/1f69476b64fb47105c06beef757766c376b548c4", + "reference": "1f69476b64fb47105c06beef757766c376b548c4", "shasum": "" }, "require": { @@ -8221,7 +8221,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/v6.3.2" + "source": "https://github.com/symfony/error-handler/tree/v6.3.5" }, "funding": [ { @@ -8237,7 +8237,7 @@ "type": "tidelift" } ], - "time": "2023-07-16T17:05:46+00:00" + "time": "2023-09-12T06:57:20+00:00" }, { "name": "symfony/event-dispatcher", @@ -8524,16 +8524,16 @@ }, { "name": "symfony/finder", - "version": "v6.3.3", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "9915db259f67d21eefee768c1abcf1cc61b1fc9e" + "reference": "a1b31d88c0e998168ca7792f222cbecee47428c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/9915db259f67d21eefee768c1abcf1cc61b1fc9e", - "reference": "9915db259f67d21eefee768c1abcf1cc61b1fc9e", + "url": "https://api.github.com/repos/symfony/finder/zipball/a1b31d88c0e998168ca7792f222cbecee47428c4", + "reference": "a1b31d88c0e998168ca7792f222cbecee47428c4", "shasum": "" }, "require": { @@ -8568,7 +8568,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.3.3" + "source": "https://github.com/symfony/finder/tree/v6.3.5" }, "funding": [ { @@ -8584,7 +8584,7 @@ "type": "tidelift" } ], - "time": "2023-07-31T08:31:44+00:00" + "time": "2023-09-26T12:56:25+00:00" }, { "name": "symfony/flex", @@ -8653,16 +8653,16 @@ }, { "name": "symfony/form", - "version": "v6.3.2", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/form.git", - "reference": "afdadf511e08bc6d4752afb869ce084276aca4e2" + "reference": "0f9ad8600c1021983d096512066ee54332aa3139" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/form/zipball/afdadf511e08bc6d4752afb869ce084276aca4e2", - "reference": "afdadf511e08bc6d4752afb869ce084276aca4e2", + "url": "https://api.github.com/repos/symfony/form/zipball/0f9ad8600c1021983d096512066ee54332aa3139", + "reference": "0f9ad8600c1021983d096512066ee54332aa3139", "shasum": "" }, "require": { @@ -8730,7 +8730,7 @@ "description": "Allows to easily create, process and reuse HTML forms", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/form/tree/v6.3.2" + "source": "https://github.com/symfony/form/tree/v6.3.5" }, "funding": [ { @@ -8746,20 +8746,20 @@ "type": "tidelift" } ], - "time": "2023-07-26T17:39:03+00:00" + "time": "2023-09-10T17:47:23+00:00" }, { "name": "symfony/framework-bundle", - "version": "v6.3.4", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "f822f54ff05cd88878910b4559f66c12176d952c" + "reference": "567cafcfc08e3076b47290a7558b0ca17a98b0ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/f822f54ff05cd88878910b4559f66c12176d952c", - "reference": "f822f54ff05cd88878910b4559f66c12176d952c", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/567cafcfc08e3076b47290a7558b0ca17a98b0ce", + "reference": "567cafcfc08e3076b47290a7558b0ca17a98b0ce", "shasum": "" }, "require": { @@ -8874,7 +8874,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/v6.3.4" + "source": "https://github.com/symfony/framework-bundle/tree/v6.3.5" }, "funding": [ { @@ -8890,20 +8890,20 @@ "type": "tidelift" } ], - "time": "2023-08-16T18:04:38+00:00" + "time": "2023-09-29T10:45:15+00:00" }, { "name": "symfony/http-client", - "version": "v6.3.2", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "15f9f4bad62bfcbe48b5dedd866f04a08fc7ff00" + "reference": "213e564da4cbf61acc9728d97e666bcdb868c10d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/15f9f4bad62bfcbe48b5dedd866f04a08fc7ff00", - "reference": "15f9f4bad62bfcbe48b5dedd866f04a08fc7ff00", + "url": "https://api.github.com/repos/symfony/http-client/zipball/213e564da4cbf61acc9728d97e666bcdb868c10d", + "reference": "213e564da4cbf61acc9728d97e666bcdb868c10d", "shasum": "" }, "require": { @@ -8966,7 +8966,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.3.2" + "source": "https://github.com/symfony/http-client/tree/v6.3.5" }, "funding": [ { @@ -8982,7 +8982,7 @@ "type": "tidelift" } ], - "time": "2023-07-05T08:41:27+00:00" + "time": "2023-09-29T15:57:12+00:00" }, { "name": "symfony/http-client-contracts", @@ -9064,16 +9064,16 @@ }, { "name": "symfony/http-foundation", - "version": "v6.3.4", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "cac1556fdfdf6719668181974104e6fcfa60e844" + "reference": "b50f5e281d722cb0f4c296f908bacc3e2b721957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/cac1556fdfdf6719668181974104e6fcfa60e844", - "reference": "cac1556fdfdf6719668181974104e6fcfa60e844", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/b50f5e281d722cb0f4c296f908bacc3e2b721957", + "reference": "b50f5e281d722cb0f4c296f908bacc3e2b721957", "shasum": "" }, "require": { @@ -9121,7 +9121,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.3.4" + "source": "https://github.com/symfony/http-foundation/tree/v6.3.5" }, "funding": [ { @@ -9137,20 +9137,20 @@ "type": "tidelift" } ], - "time": "2023-08-22T08:20:46+00:00" + "time": "2023-09-04T21:33:54+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.3.4", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "36abb425b4af863ae1fe54d8a8b8b4c76a2bccdb" + "reference": "9f991a964368bee8d883e8d57ced4fe9fff04dfc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/36abb425b4af863ae1fe54d8a8b8b4c76a2bccdb", - "reference": "36abb425b4af863ae1fe54d8a8b8b4c76a2bccdb", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/9f991a964368bee8d883e8d57ced4fe9fff04dfc", + "reference": "9f991a964368bee8d883e8d57ced4fe9fff04dfc", "shasum": "" }, "require": { @@ -9234,7 +9234,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/v6.3.4" + "source": "https://github.com/symfony/http-kernel/tree/v6.3.5" }, "funding": [ { @@ -9250,7 +9250,7 @@ "type": "tidelift" } ], - "time": "2023-08-26T13:54:49+00:00" + "time": "2023-09-30T06:37:04+00:00" }, { "name": "symfony/intl", @@ -9336,16 +9336,16 @@ }, { "name": "symfony/mailer", - "version": "v6.3.0", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "7b03d9be1dea29bfec0a6c7b603f5072a4c97435" + "reference": "d89611a7830d51b5e118bca38e390dea92f9ea06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/7b03d9be1dea29bfec0a6c7b603f5072a4c97435", - "reference": "7b03d9be1dea29bfec0a6c7b603f5072a4c97435", + "url": "https://api.github.com/repos/symfony/mailer/zipball/d89611a7830d51b5e118bca38e390dea92f9ea06", + "reference": "d89611a7830d51b5e118bca38e390dea92f9ea06", "shasum": "" }, "require": { @@ -9396,7 +9396,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v6.3.0" + "source": "https://github.com/symfony/mailer/tree/v6.3.5" }, "funding": [ { @@ -9412,20 +9412,20 @@ "type": "tidelift" } ], - "time": "2023-05-29T12:49:39+00:00" + "time": "2023-09-06T09:47:15+00:00" }, { "name": "symfony/mime", - "version": "v6.3.3", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "9a0cbd52baa5ba5a5b1f0cacc59466f194730f98" + "reference": "d5179eedf1cb2946dbd760475ebf05c251ef6a6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/9a0cbd52baa5ba5a5b1f0cacc59466f194730f98", - "reference": "9a0cbd52baa5ba5a5b1f0cacc59466f194730f98", + "url": "https://api.github.com/repos/symfony/mime/zipball/d5179eedf1cb2946dbd760475ebf05c251ef6a6e", + "reference": "d5179eedf1cb2946dbd760475ebf05c251ef6a6e", "shasum": "" }, "require": { @@ -9480,7 +9480,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.3.3" + "source": "https://github.com/symfony/mime/tree/v6.3.5" }, "funding": [ { @@ -9496,7 +9496,7 @@ "type": "tidelift" } ], - "time": "2023-07-31T07:08:24+00:00" + "time": "2023-09-29T06:59:36+00:00" }, { "name": "symfony/monolog-bridge", @@ -9726,16 +9726,16 @@ }, { "name": "symfony/password-hasher", - "version": "v6.3.0", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/password-hasher.git", - "reference": "d23ad221989e6b8278d050cabfd7b569eee84590" + "reference": "278d3a49715073879f75e372ad80b8cfeca949d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/password-hasher/zipball/d23ad221989e6b8278d050cabfd7b569eee84590", - "reference": "d23ad221989e6b8278d050cabfd7b569eee84590", + "url": "https://api.github.com/repos/symfony/password-hasher/zipball/278d3a49715073879f75e372ad80b8cfeca949d3", + "reference": "278d3a49715073879f75e372ad80b8cfeca949d3", "shasum": "" }, "require": { @@ -9778,7 +9778,7 @@ "password" ], "support": { - "source": "https://github.com/symfony/password-hasher/tree/v6.3.0" + "source": "https://github.com/symfony/password-hasher/tree/v6.3.5" }, "funding": [ { @@ -9794,7 +9794,7 @@ "type": "tidelift" } ], - "time": "2023-02-14T09:04:20+00:00" + "time": "2023-09-25T17:05:16+00:00" }, { "name": "symfony/polyfill-ctype", @@ -11070,16 +11070,16 @@ }, { "name": "symfony/routing", - "version": "v6.3.3", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "e7243039ab663822ff134fbc46099b5fdfa16f6a" + "reference": "82616e59acd3e3d9c916bba798326cb7796d7d31" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/e7243039ab663822ff134fbc46099b5fdfa16f6a", - "reference": "e7243039ab663822ff134fbc46099b5fdfa16f6a", + "url": "https://api.github.com/repos/symfony/routing/zipball/82616e59acd3e3d9c916bba798326cb7796d7d31", + "reference": "82616e59acd3e3d9c916bba798326cb7796d7d31", "shasum": "" }, "require": { @@ -11133,7 +11133,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.3.3" + "source": "https://github.com/symfony/routing/tree/v6.3.5" }, "funding": [ { @@ -11149,7 +11149,7 @@ "type": "tidelift" } ], - "time": "2023-07-31T07:08:24+00:00" + "time": "2023-09-20T16:05:51+00:00" }, { "name": "symfony/runtime", @@ -11232,16 +11232,16 @@ }, { "name": "symfony/security-bundle", - "version": "v6.3.4", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/security-bundle.git", - "reference": "31957477b289220a47880ead3727bf5cc059fa08" + "reference": "2df460eacceb11b9287cfafddda4d27023dd9001" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-bundle/zipball/31957477b289220a47880ead3727bf5cc059fa08", - "reference": "31957477b289220a47880ead3727bf5cc059fa08", + "url": "https://api.github.com/repos/symfony/security-bundle/zipball/2df460eacceb11b9287cfafddda4d27023dd9001", + "reference": "2df460eacceb11b9287cfafddda4d27023dd9001", "shasum": "" }, "require": { @@ -11258,7 +11258,7 @@ "symfony/password-hasher": "^5.4|^6.0", "symfony/security-core": "^6.2", "symfony/security-csrf": "^5.4|^6.0", - "symfony/security-http": "^6.3" + "symfony/security-http": "^6.3.4" }, "conflict": { "symfony/browser-kit": "<5.4", @@ -11322,7 +11322,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/v6.3.4" + "source": "https://github.com/symfony/security-bundle/tree/v6.3.5" }, "funding": [ { @@ -11338,20 +11338,20 @@ "type": "tidelift" } ], - "time": "2023-08-25T08:46:23+00:00" + "time": "2023-09-25T17:05:55+00:00" }, { "name": "symfony/security-core", - "version": "v6.3.3", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/security-core.git", - "reference": "b86ce012cc9a62a15ed43af5037eebc3e6de4d7f" + "reference": "ec8f24dc1195f46483510892271d01a5202bba70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-core/zipball/b86ce012cc9a62a15ed43af5037eebc3e6de4d7f", - "reference": "b86ce012cc9a62a15ed43af5037eebc3e6de4d7f", + "url": "https://api.github.com/repos/symfony/security-core/zipball/ec8f24dc1195f46483510892271d01a5202bba70", + "reference": "ec8f24dc1195f46483510892271d01a5202bba70", "shasum": "" }, "require": { @@ -11407,7 +11407,7 @@ "description": "Symfony Security Component - Core Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-core/tree/v6.3.3" + "source": "https://github.com/symfony/security-core/tree/v6.3.5" }, "funding": [ { @@ -11423,7 +11423,7 @@ "type": "tidelift" } ], - "time": "2023-07-31T07:08:24+00:00" + "time": "2023-09-10T17:47:23+00:00" }, { "name": "symfony/security-csrf", @@ -11495,16 +11495,16 @@ }, { "name": "symfony/security-http", - "version": "v6.3.4", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/security-http.git", - "reference": "0afb37c1120c1c46219bdbd1dd912fb4d48eaf7d" + "reference": "47058ea557a4c64ba86e9249651222842bd52e2a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-http/zipball/0afb37c1120c1c46219bdbd1dd912fb4d48eaf7d", - "reference": "0afb37c1120c1c46219bdbd1dd912fb4d48eaf7d", + "url": "https://api.github.com/repos/symfony/security-http/zipball/47058ea557a4c64ba86e9249651222842bd52e2a", + "reference": "47058ea557a4c64ba86e9249651222842bd52e2a", "shasum": "" }, "require": { @@ -11562,7 +11562,7 @@ "description": "Symfony Security Component - HTTP Integration", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-http/tree/v6.3.4" + "source": "https://github.com/symfony/security-http/tree/v6.3.5" }, "funding": [ { @@ -11578,20 +11578,20 @@ "type": "tidelift" } ], - "time": "2023-08-25T19:43:09+00:00" + "time": "2023-08-30T06:30:46+00:00" }, { "name": "symfony/serializer", - "version": "v6.3.4", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "96d28a58d5a128bf77c54534b380eb7c92c8f846" + "reference": "855fc058c8bdbb69f53834f2fdb3876c9bc0ab7c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/96d28a58d5a128bf77c54534b380eb7c92c8f846", - "reference": "96d28a58d5a128bf77c54534b380eb7c92c8f846", + "url": "https://api.github.com/repos/symfony/serializer/zipball/855fc058c8bdbb69f53834f2fdb3876c9bc0ab7c", + "reference": "855fc058c8bdbb69f53834f2fdb3876c9bc0ab7c", "shasum": "" }, "require": { @@ -11656,7 +11656,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/v6.3.4" + "source": "https://github.com/symfony/serializer/tree/v6.3.5" }, "funding": [ { @@ -11672,7 +11672,7 @@ "type": "tidelift" } ], - "time": "2023-08-24T14:35:28+00:00" + "time": "2023-09-29T16:18:53+00:00" }, { "name": "symfony/service-contracts", @@ -11758,7 +11758,7 @@ }, { "name": "symfony/stimulus-bundle", - "version": "v2.11.1", + "version": "v2.12.0", "source": { "type": "git", "url": "https://github.com/symfony/stimulus-bundle.git", @@ -11806,7 +11806,7 @@ "symfony-ux" ], "support": { - "source": "https://github.com/symfony/stimulus-bundle/tree/v2.11.1" + "source": "https://github.com/symfony/stimulus-bundle/tree/v2.12.0" }, "funding": [ { @@ -11888,16 +11888,16 @@ }, { "name": "symfony/string", - "version": "v6.3.2", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "53d1a83225002635bca3482fcbf963001313fb68" + "reference": "13d76d0fb049051ed12a04bef4f9de8715bea339" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/53d1a83225002635bca3482fcbf963001313fb68", - "reference": "53d1a83225002635bca3482fcbf963001313fb68", + "url": "https://api.github.com/repos/symfony/string/zipball/13d76d0fb049051ed12a04bef4f9de8715bea339", + "reference": "13d76d0fb049051ed12a04bef4f9de8715bea339", "shasum": "" }, "require": { @@ -11954,7 +11954,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.3.2" + "source": "https://github.com/symfony/string/tree/v6.3.5" }, "funding": [ { @@ -11970,7 +11970,7 @@ "type": "tidelift" } ], - "time": "2023-07-05T08:41:27+00:00" + "time": "2023-09-18T10:38:32+00:00" }, { "name": "symfony/translation", @@ -12147,16 +12147,16 @@ }, { "name": "symfony/twig-bridge", - "version": "v6.3.2", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "6f8435db76a2d79917489a19a82679276c1b4e32" + "reference": "18f2cbe1d46ad43c4d3bd45e5e6279172068e064" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/6f8435db76a2d79917489a19a82679276c1b4e32", - "reference": "6f8435db76a2d79917489a19a82679276c1b4e32", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/18f2cbe1d46ad43c4d3bd45e5e6279172068e064", + "reference": "18f2cbe1d46ad43c4d3bd45e5e6279172068e064", "shasum": "" }, "require": { @@ -12235,7 +12235,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v6.3.2" + "source": "https://github.com/symfony/twig-bridge/tree/v6.3.5" }, "funding": [ { @@ -12251,7 +12251,7 @@ "type": "tidelift" } ], - "time": "2023-07-20T16:42:33+00:00" + "time": "2023-09-12T06:57:20+00:00" }, { "name": "symfony/twig-bundle", @@ -12414,7 +12414,7 @@ }, { "name": "symfony/ux-translator", - "version": "v2.11.1", + "version": "v2.12.0", "source": { "type": "git", "url": "https://github.com/symfony/ux-translator.git", @@ -12470,7 +12470,7 @@ "symfony-ux" ], "support": { - "source": "https://github.com/symfony/ux-translator/tree/v2.11.1" + "source": "https://github.com/symfony/ux-translator/tree/v2.12.0" }, "funding": [ { @@ -12490,16 +12490,16 @@ }, { "name": "symfony/ux-turbo", - "version": "v2.11.2", + "version": "v2.12.0", "source": { "type": "git", "url": "https://github.com/symfony/ux-turbo.git", - "reference": "b245cb9d0682a1ddb287d496337420a40d0cb2a6" + "reference": "abd29f26acff6a0032f0494e0d31a7415358bdd6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ux-turbo/zipball/b245cb9d0682a1ddb287d496337420a40d0cb2a6", - "reference": "b245cb9d0682a1ddb287d496337420a40d0cb2a6", + "url": "https://api.github.com/repos/symfony/ux-turbo/zipball/abd29f26acff6a0032f0494e0d31a7415358bdd6", + "reference": "abd29f26acff6a0032f0494e0d31a7415358bdd6", "shasum": "" }, "require": { @@ -12566,7 +12566,7 @@ "turbo-stream" ], "support": { - "source": "https://github.com/symfony/ux-turbo/tree/v2.11.2" + "source": "https://github.com/symfony/ux-turbo/tree/v2.12.0" }, "funding": [ { @@ -12582,20 +12582,20 @@ "type": "tidelift" } ], - "time": "2023-09-06T19:09:16+00:00" + "time": "2023-09-18T18:10:53+00:00" }, { "name": "symfony/validator", - "version": "v6.3.4", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "0c8435154920b9bbe93bece675234c244cadf73b" + "reference": "48e815ba3b5eb72e632588dbf7ea2dc4e608ee47" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/0c8435154920b9bbe93bece675234c244cadf73b", - "reference": "0c8435154920b9bbe93bece675234c244cadf73b", + "url": "https://api.github.com/repos/symfony/validator/zipball/48e815ba3b5eb72e632588dbf7ea2dc4e608ee47", + "reference": "48e815ba3b5eb72e632588dbf7ea2dc4e608ee47", "shasum": "" }, "require": { @@ -12662,7 +12662,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v6.3.4" + "source": "https://github.com/symfony/validator/tree/v6.3.5" }, "funding": [ { @@ -12678,20 +12678,20 @@ "type": "tidelift" } ], - "time": "2023-08-17T15:49:05+00:00" + "time": "2023-09-29T07:41:15+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.3.4", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "2027be14f8ae8eae999ceadebcda5b4909b81d45" + "reference": "3d9999376be5fea8de47752837a3e1d1c5f69ef5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/2027be14f8ae8eae999ceadebcda5b4909b81d45", - "reference": "2027be14f8ae8eae999ceadebcda5b4909b81d45", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/3d9999376be5fea8de47752837a3e1d1c5f69ef5", + "reference": "3d9999376be5fea8de47752837a3e1d1c5f69ef5", "shasum": "" }, "require": { @@ -12746,7 +12746,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.3.4" + "source": "https://github.com/symfony/var-dumper/tree/v6.3.5" }, "funding": [ { @@ -12762,7 +12762,7 @@ "type": "tidelift" } ], - "time": "2023-08-24T14:51:05+00:00" + "time": "2023-09-12T10:11:35+00:00" }, { "name": "symfony/var-exporter", @@ -12923,31 +12923,31 @@ }, { "name": "symfony/webpack-encore-bundle", - "version": "v2.0.1", + "version": "v2.1.0", "source": { "type": "git", "url": "https://github.com/symfony/webpack-encore-bundle.git", - "reference": "150fe022740fef908f4ca3d5950ce85ab040ec76" + "reference": "50d4af5cca35dab66a51dfeeb63616937ba96f4a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/webpack-encore-bundle/zipball/150fe022740fef908f4ca3d5950ce85ab040ec76", - "reference": "150fe022740fef908f4ca3d5950ce85ab040ec76", + "url": "https://api.github.com/repos/symfony/webpack-encore-bundle/zipball/50d4af5cca35dab66a51dfeeb63616937ba96f4a", + "reference": "50d4af5cca35dab66a51dfeeb63616937ba96f4a", "shasum": "" }, "require": { "php": ">=8.1.0", - "symfony/asset": "^5.4 || ^6.2", - "symfony/config": "^5.4 || ^6.2", - "symfony/dependency-injection": "^5.4 || ^6.2", - "symfony/http-kernel": "^5.4 || ^6.2", + "symfony/asset": "^5.4 || ^6.2 || ^7.0", + "symfony/config": "^5.4 || ^6.2 || ^7.0", + "symfony/dependency-injection": "^5.4 || ^6.2 || ^7.0", + "symfony/http-kernel": "^5.4 || ^6.2 || ^7.0", "symfony/service-contracts": "^1.1.9 || ^2.1.3 || ^3.0" }, "require-dev": { - "symfony/framework-bundle": "^5.4 || ^6.2", - "symfony/phpunit-bridge": "^5.4 || ^6.2", - "symfony/twig-bundle": "^5.4 || ^6.2", - "symfony/web-link": "^5.4 || ^6.2" + "symfony/framework-bundle": "^5.4 || ^6.2 || ^7.0", + "symfony/phpunit-bridge": "^5.4 || ^6.2 || ^7.0", + "symfony/twig-bundle": "^5.4 || ^6.2 || ^7.0", + "symfony/web-link": "^5.4 || ^6.2 || ^7.0" }, "type": "symfony-bundle", "extra": { @@ -12974,7 +12974,7 @@ "description": "Integration with your Symfony app & Webpack Encore!", "support": { "issues": "https://github.com/symfony/webpack-encore-bundle/issues", - "source": "https://github.com/symfony/webpack-encore-bundle/tree/v2.0.1" + "source": "https://github.com/symfony/webpack-encore-bundle/tree/v2.1.0" }, "funding": [ { @@ -12990,7 +12990,7 @@ "type": "tidelift" } ], - "time": "2023-05-31T14:28:33+00:00" + "time": "2023-09-26T14:41:29+00:00" }, { "name": "symfony/yaml", @@ -14367,79 +14367,6 @@ }, "time": "2022-06-03T18:03:27+00:00" }, - { - "name": "webonyx/graphql-php", - "version": "v15.6.3", - "source": { - "type": "git", - "url": "https://github.com/webonyx/graphql-php.git", - "reference": "3f045a4626d179ea6462e1a11744619860b55eb8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/3f045a4626d179ea6462e1a11744619860b55eb8", - "reference": "3f045a4626d179ea6462e1a11744619860b55eb8", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-mbstring": "*", - "php": "^7.4 || ^8" - }, - "require-dev": { - "amphp/amp": "^2.6", - "amphp/http-server": "^2.1", - "dms/phpunit-arraysubset-asserts": "dev-master", - "ergebnis/composer-normalize": "^2.28", - "mll-lab/php-cs-fixer-config": "^5", - "nyholm/psr7": "^1.5", - "phpbench/phpbench": "^1.2", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "1.10.32", - "phpstan/phpstan-phpunit": "1.3.14", - "phpstan/phpstan-strict-rules": "1.5.1", - "phpunit/phpunit": "^9.5 || ^10", - "psr/http-message": "^1 || ^2", - "react/http": "^1.6", - "react/promise": "^2.9", - "rector/rector": "^0.18", - "symfony/polyfill-php81": "^1.23", - "symfony/var-exporter": "^5 || ^6", - "thecodingmachine/safe": "^1.3 || ^2" - }, - "suggest": { - "amphp/http-server": "To leverage async resolving with webserver on AMPHP platform", - "psr/http-message": "To use standard GraphQL server", - "react/promise": "To leverage async resolving on React PHP platform" - }, - "type": "library", - "autoload": { - "psr-4": { - "GraphQL\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A PHP port of GraphQL reference implementation", - "homepage": "https://github.com/webonyx/graphql-php", - "keywords": [ - "api", - "graphql" - ], - "support": { - "issues": "https://github.com/webonyx/graphql-php/issues", - "source": "https://github.com/webonyx/graphql-php/tree/v15.6.3" - }, - "funding": [ - { - "url": "https://opencollective.com/webonyx-graphql-php", - "type": "open_collective" - } - ], - "time": "2023-09-01T08:13:33+00:00" - }, { "name": "willdurand/negotiation", "version": "3.1.0", @@ -15393,16 +15320,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.34", + "version": "1.10.36", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "7f806b6f1403e6914c778140e2ba07c293cb4901" + "reference": "ffa3089511121a672e62969404e4fddc753f9b15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/7f806b6f1403e6914c778140e2ba07c293cb4901", - "reference": "7f806b6f1403e6914c778140e2ba07c293cb4901", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ffa3089511121a672e62969404e4fddc753f9b15", + "reference": "ffa3089511121a672e62969404e4fddc753f9b15", "shasum": "" }, "require": { @@ -15451,7 +15378,7 @@ "type": "tidelift" } ], - "time": "2023-09-13T09:49:47+00:00" + "time": "2023-09-29T14:07:45+00:00" }, { "name": "phpstan/phpstan-doctrine", @@ -15574,22 +15501,22 @@ }, { "name": "phpstan/phpstan-symfony", - "version": "1.3.2", + "version": "1.3.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "7332b90dfc291ac5b4b83fbca2081936faa1e3f9" + "reference": "383855999db6a7d65d0bf580ce2762e17188c2a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/7332b90dfc291ac5b4b83fbca2081936faa1e3f9", - "reference": "7332b90dfc291ac5b4b83fbca2081936faa1e3f9", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/383855999db6a7d65d0bf580ce2762e17188c2a5", + "reference": "383855999db6a7d65d0bf580ce2762e17188c2a5", "shasum": "" }, "require": { "ext-simplexml": "*", "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.9.18" + "phpstan/phpstan": "^1.10.36" }, "conflict": { "symfony/framework-bundle": "<3.0" @@ -15597,8 +15524,8 @@ "require-dev": { "nikic/php-parser": "^4.13.0", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^1.0", - "phpstan/phpstan-strict-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.3.11", + "phpstan/phpstan-strict-rules": "^1.5.1", "phpunit/phpunit": "^8.5.29 || ^9.5", "psr/container": "1.0 || 1.1.1", "symfony/config": "^5.4 || ^6.1", @@ -15609,7 +15536,8 @@ "symfony/http-foundation": "^5.4 || ^6.1", "symfony/messenger": "^5.4", "symfony/polyfill-php80": "^1.24", - "symfony/serializer": "^5.4" + "symfony/serializer": "^5.4", + "symfony/service-contracts": "^2.2.0" }, "type": "phpstan-extension", "extra": { @@ -15639,9 +15567,9 @@ "description": "Symfony Framework extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-symfony/issues", - "source": "https://github.com/phpstan/phpstan-symfony/tree/1.3.2" + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.3.4" }, - "time": "2023-05-16T12:46:15+00:00" + "time": "2023-09-29T14:10:11+00:00" }, { "name": "psalm/plugin-symfony", @@ -15710,16 +15638,16 @@ }, { "name": "rector/rector", - "version": "0.18.3", + "version": "0.18.4", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "ba7988e3e028e68e07191d75b0d5473ac320c5e7" + "reference": "d99a91176b7eb7f2b6d509a6486b3661c6dfd370" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/ba7988e3e028e68e07191d75b0d5473ac320c5e7", - "reference": "ba7988e3e028e68e07191d75b0d5473ac320c5e7", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/d99a91176b7eb7f2b6d509a6486b3661c6dfd370", + "reference": "d99a91176b7eb7f2b6d509a6486b3661c6dfd370", "shasum": "" }, "require": { @@ -15754,7 +15682,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/0.18.3" + "source": "https://github.com/rectorphp/rector/tree/0.18.4" }, "funding": [ { @@ -15762,7 +15690,7 @@ "type": "github" } ], - "time": "2023-09-12T20:18:14+00:00" + "time": "2023-09-25T17:07:54+00:00" }, { "name": "roave/security-advisories", @@ -15770,12 +15698,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "a4a221e6b171fe5eadf48e21ad8247304738bcd5" + "reference": "2bd71aadfc5003f371b9cfd93844a73036279798" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/a4a221e6b171fe5eadf48e21ad8247304738bcd5", - "reference": "a4a221e6b171fe5eadf48e21ad8247304738bcd5", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/2bd71aadfc5003f371b9cfd93844a73036279798", + "reference": "2bd71aadfc5003f371b9cfd93844a73036279798", "shasum": "" }, "conflict": { @@ -15853,7 +15781,7 @@ "codeigniter4/framework": "<4.3.5", "codeigniter4/shield": "<1.0.0.0-beta4", "codiad/codiad": "<=2.8.4", - "composer/composer": "<1.10.26|>=2,<2.2.12|>=2.3,<2.3.5", + "composer/composer": "<1.10.27|>=2,<2.2.22|>=2.3,<2.6.4", "concrete5/concrete5": "<9.2", "concrete5/core": "<8.5.8|>=9,<9.1", "contao-components/mediaelement": ">=2.14.2,<2.21.1", @@ -15915,7 +15843,7 @@ "ezsystems/ezplatform-richtext": ">=2.3,<2.3.7.1-dev", "ezsystems/ezplatform-user": ">=1,<1.0.1", "ezsystems/ezpublish-kernel": "<6.13.8.2-dev|>=7,<7.5.30", - "ezsystems/ezpublish-legacy": "<=2017.12.7.3|>=2018.06,<=2019.03.5.1", + "ezsystems/ezpublish-legacy": "<=2017.12.7.3|>=2018.6,<=2019.03.5.1", "ezsystems/platform-ui-assets-bundle": ">=4.2,<4.2.3", "ezsystems/repository-forms": ">=2.3,<2.3.2.1-dev|>=2.5,<2.5.15", "ezyang/htmlpurifier": "<4.1.1", @@ -15958,7 +15886,7 @@ "getkirby/panel": "<2.5.14", "getkirby/starterkit": "<=3.7.0.2", "gilacms/gila": "<=1.11.4", - "gleez/cms": "<=1.2", + "gleez/cms": "<=1.2|==2", "globalpayments/php-sdk": "<2", "gogentooss/samlbase": "<1.2.7", "google/protobuf": "<3.15", @@ -15966,6 +15894,7 @@ "gree/jose": "<2.2.1", "gregwar/rst": "<1.0.3", "grumpydictator/firefly-iii": "<6", + "gugoan/economizzer": "<=0.9.0.0-beta1", "guzzlehttp/guzzle": "<6.5.8|>=7,<7.4.5", "guzzlehttp/psr7": "<1.9.1|>=2,<2.4.5", "haffner/jh_captcha": "<=2.1.3|>=3,<=3.0.2", @@ -15995,7 +15924,7 @@ "in2code/ipandlanguageredirect": "<5.1.2", "in2code/lux": "<17.6.1|>=18,<24.0.2", "innologi/typo3-appointments": "<2.0.6", - "intelliants/subrion": "<=4.2.1", + "intelliants/subrion": "<4.2.2", "islandora/islandora": ">=2,<2.4.1", "ivankristianto/phpwhois": "<=4.3", "jackalope/jackalope-doctrine-dbal": "<1.7.4", @@ -16003,6 +15932,7 @@ "james-heinrich/phpthumb": "<1.7.12", "jasig/phpcas": "<1.3.3", "jcbrand/converse.js": "<3.3.3", + "joomla/application": "<1.0.13", "joomla/archive": "<1.1.12|>=2,<2.0.1", "joomla/filesystem": "<1.6.2|>=2,<2.0.1", "joomla/filter": "<1.4.4|>=2,<2.0.1", @@ -16080,6 +16010,7 @@ "neos/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", "neos/form": ">=1.2,<4.3.3|>=5,<5.0.9|>=5.1,<5.1.3", "neos/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.9.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<5.3.10|>=7,<7.0.9|>=7.1,<7.1.7|>=7.2,<7.2.6|>=7.3,<7.3.4|>=8,<8.0.2", + "neos/neos-ui": "<=8.3.3", "neos/swiftmailer": ">=4.1,<4.1.99|>=5.4,<5.4.5", "netgen/tagsbundle": ">=3.4,<3.4.11|>=4,<4.0.15", "nette/application": ">=2,<2.0.19|>=2.1,<2.1.13|>=2.2,<2.2.10|>=2.3,<2.3.14|>=2.4,<2.4.16|>=3,<3.0.6", @@ -16092,14 +16023,14 @@ "nystudio107/craft-seomatic": "<3.4.12", "nzo/url-encryptor-bundle": ">=4,<4.3.2|>=5,<5.0.1", "october/backend": "<1.1.2", - "october/cms": "<1.0.469|==1.0.469|==1.0.471|==1.1.1", + "october/cms": "<=3.4.16", "october/october": "<=3.4.4", "october/rain": "<1.0.472|>=1.1,<1.1.2", "october/system": "<1.0.476|>=1.1,<1.1.12|>=2,<2.2.34|>=3,<3.0.66", "onelogin/php-saml": "<2.10.4", "oneup/uploader-bundle": "<1.9.3|>=2,<2.1.5", "open-web-analytics/open-web-analytics": "<1.7.4", - "opencart/opencart": "<=3.0.3.7", + "opencart/opencart": "<=3.0.3.7|>=4,<4.0.2.3-dev", "openid/php-openid": "<2.3", "openmage/magento-lts": "<=19.5|>=20,<=20.1", "opensource-workshop/connect-cms": "<1.7.2|>=2,<2.3.2", @@ -16138,9 +16069,10 @@ "phpxmlrpc/extras": "<0.6.1", "phpxmlrpc/phpxmlrpc": "<4.9.2", "pi/pi": "<=2.5", - "pimcore/admin-ui-classic-bundle": "<1.0.3", + "pimcore/admin-ui-classic-bundle": "<1.1.2", "pimcore/customer-management-framework-bundle": "<3.4.2", "pimcore/data-hub": "<1.2.4", + "pimcore/demo": "<10.3", "pimcore/perspective-editor": "<1.5.1", "pimcore/pimcore": "<10.6.8", "pixelfed/pixelfed": "<=0.11.4", @@ -16151,7 +16083,7 @@ "prestashop/blockwishlist": ">=2,<2.1.1", "prestashop/contactform": ">=1.0.1,<4.3", "prestashop/gamification": "<2.3.2", - "prestashop/prestashop": "<=8.1", + "prestashop/prestashop": "<8.1.2", "prestashop/productcomments": "<5.0.2", "prestashop/ps_emailsubscription": "<2.6.1", "prestashop/ps_facetedsearch": "<3.4.1", @@ -16218,7 +16150,7 @@ "simplesamlphp/simplesamlphp-module-openidprovider": "<0.9", "simplito/elliptic-php": "<1.0.6", "sitegeist/fluid-components": "<3.5", - "sjbr/sr-freecap": "<=2.5.2", + "sjbr/sr-freecap": "<2.4.6|>=2.5,<2.5.3", "slim/psr7": "<1.4.1|>=1.5,<1.5.1|>=1.6,<1.6.1", "slim/slim": "<2.6", "slub/slub-events": "<3.0.3", @@ -16236,7 +16168,6 @@ "stormpath/sdk": "<9.9.99", "studio-42/elfinder": "<2.1.62", "subhh/libconnect": "<7.0.8|>=8,<8.1", - "subrion/cms": "<=4.2.1", "sukohi/surpass": "<1", "sulu/sulu": "<1.6.44|>=2,<2.2.18|>=2.3,<2.3.8|==2.4.0.0-RC1|>=2.5,<2.5.10", "sumocoders/framework-user-bundle": "<1.4", @@ -16306,7 +16237,7 @@ "truckersmp/phpwhois": "<=4.3.1", "ttskch/pagination-service-provider": "<1", "twig/twig": "<1.44.7|>=2,<2.15.3|>=3,<3.4.3", - "typo3/cms": "<8.7.38|>=9,<9.5.29|>=10,<10.4.35|>=11,<11.5.23|>=12,<12.2", + "typo3/cms": "<9.5.29|>=10,<10.4.35|>=11,<11.5.23|>=12,<12.2", "typo3/cms-backend": ">=7,<=7.6.50|>=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1", "typo3/cms-core": "<8.7.51|>=9,<9.5.42|>=10,<10.4.39|>=11,<11.5.30|>=12,<12.4.4", "typo3/cms-extbase": "<6.2.24|>=7,<7.6.8|==8.1.1", @@ -16386,7 +16317,15 @@ "zendframework/zend-xmlrpc": ">=2.1,<2.1.6|>=2.2,<2.2.6", "zendframework/zendframework": "<=3", "zendframework/zendframework1": "<1.12.20", - "zendframework/zendopenid": ">=2,<2.0.2", + "zendframework/zendopenid": "<2.0.2", + "zendframework/zendrest": "<2.0.2", + "zendframework/zendservice-amazon": "<2.0.3", + "zendframework/zendservice-api": "<1", + "zendframework/zendservice-audioscrobbler": "<2.0.2", + "zendframework/zendservice-nirvanix": "<2.0.2", + "zendframework/zendservice-slideshare": "<2.0.2", + "zendframework/zendservice-technorati": "<2.0.2", + "zendframework/zendservice-windowsazure": "<2.0.2", "zendframework/zendxml": "<1.0.1", "zenstruck/collection": "<0.2.1", "zetacomponents/mail": "<1.8.2", @@ -16431,7 +16370,7 @@ "type": "tidelift" } ], - "time": "2023-09-15T19:04:11+00:00" + "time": "2023-09-29T21:04:27+00:00" }, { "name": "sebastian/diff", @@ -16774,16 +16713,16 @@ }, { "name": "symfony/maker-bundle", - "version": "v1.51.0", + "version": "v1.51.1", "source": { "type": "git", "url": "https://github.com/symfony/maker-bundle.git", - "reference": "d5684e5628d545a67ba833adcd216e7029f3e506" + "reference": "0890fd3cf1e2a5221f9b3c6ee1769c537aef683d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/d5684e5628d545a67ba833adcd216e7029f3e506", - "reference": "d5684e5628d545a67ba833adcd216e7029f3e506", + "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/0890fd3cf1e2a5221f9b3c6ee1769c537aef683d", + "reference": "0890fd3cf1e2a5221f9b3c6ee1769c537aef683d", "shasum": "" }, "require": { @@ -16846,7 +16785,7 @@ ], "support": { "issues": "https://github.com/symfony/maker-bundle/issues", - "source": "https://github.com/symfony/maker-bundle/tree/v1.51.0" + "source": "https://github.com/symfony/maker-bundle/tree/v1.51.1" }, "funding": [ { @@ -16862,7 +16801,7 @@ "type": "tidelift" } ], - "time": "2023-09-12T18:09:19+00:00" + "time": "2023-09-18T18:17:31+00:00" }, { "name": "symfony/phpunit-bridge", From 469f9e89336ecddf63f6aebd59014cec08409320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 1 Oct 2023 16:07:42 +0200 Subject: [PATCH 45/62] Do not redirect missing API prefixed routes to a localized version --- config/routes.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/routes.yaml b/config/routes.yaml index 2da939a1..8b38fa71 100644 --- a/config/routes.yaml +++ b/config/routes.yaml @@ -15,5 +15,5 @@ redirector: requirements: url: ".*" controller: App\Controller\RedirectController::addLocalePart - # Dont match localized routes (no redirection loop, if no root with that name exists) - condition: "not (request.getPathInfo() matches '/^\\\\/[a-z]{2}(_[A-Z]{2})?\\\\//')" \ No newline at end of file + # 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)\\\\//')" \ No newline at end of file From 1e04ee14deb5f0ee9b230f75009740b97a30bc72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 1 Oct 2023 21:46:33 +0200 Subject: [PATCH 46/62] Added phpstan as local dev dependency --- composer.json | 1 + composer.lock | 1565 ++++++++++++++++++++++++++++++++++++++++++++++++- symfony.lock | 14 + 3 files changed, 1567 insertions(+), 13 deletions(-) diff --git a/composer.json b/composer.json index 84572c0b..a16706b9 100644 --- a/composer.json +++ b/composer.json @@ -97,6 +97,7 @@ "phpstan/phpstan-doctrine": "^1.2.11", "phpstan/phpstan-strict-rules": "^1.5", "phpstan/phpstan-symfony": "^1.1.7", + "phpunit/phpunit": "^9.5", "psalm/plugin-symfony": "^v5.0.1", "rector/rector": "^0.18.0", "roave/security-advisories": "dev-latest", diff --git a/composer.lock b/composer.lock index dac0b4bd..23a50eb8 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": "9b3518d81d4db4a9d053779667af53a3", + "content-hash": "a3d850bada24e7349d2181693a50c637", "packages": [ { "name": "api-platform/core", @@ -15223,6 +15223,65 @@ ], "time": "2022-12-24T12:35:10+00:00" }, + { + "name": "myclabs/deep-copy", + "version": "1.11.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2023-03-08T13:26:56+00:00" + }, { "name": "netresearch/jsonmapper", "version": "v4.2.0", @@ -15274,6 +15333,117 @@ }, "time": "2023-04-09T17:37:40+00:00" }, + { + "name": "phar-io/manifest", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.3" + }, + "time": "2021-07-20T11:28:43+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, { "name": "phpstan/extension-installer", "version": "1.3.1", @@ -15571,6 +15741,428 @@ }, "time": "2023-09-29T14:10:11+00:00" }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.29", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6a3a87ac2bbe33b25042753df8195ba4aa534c76", + "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.15", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.29" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-09-19T04:57:46+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.6.13", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f3d767f7f9e191eab4189abe41ab37797e30b1be", + "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1 || ^2", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.28", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.8", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.5", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^3.2", + "sebastian/version": "^3.0.2" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.6-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.13" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2023-09-19T05:39:22+00:00" + }, { "name": "psalm/plugin-symfony", "version": "v5.0.3", @@ -16373,30 +16965,328 @@ "time": "2023-09-29T21:04:27+00:00" }, { - "name": "sebastian/diff", - "version": "5.0.3", + "name": "sebastian/cli-parser", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "912dc2fbe3e3c1e7873313cc801b100b6c68c87b" + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/912dc2fbe3e3c1e7873313cc801b100b6c68c87b", - "reference": "912dc2fbe3e3c1e7873313cc801b100b6c68c87b", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^10.0", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:08:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T12:41:17+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.7", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:52:27+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -16428,8 +17318,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/5.0.3" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" }, "funding": [ { @@ -16437,7 +17326,607 @@ "type": "github" } ], - "time": "2023-05-01T07:48:21+00:00" + "time": "2023-05-07T05:35:17+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:03:51+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T06:03:37+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bde739e7565280bda77be70044ac1047bc007e34" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", + "reference": "bde739e7565280bda77be70044ac1047bc007e34", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-02T09:26:13+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.6", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-28T06:42:11+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:07:39+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" + }, + { + "name": "sebastian/type", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:13:03+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" }, { "name": "spatie/array-to-xml", @@ -17023,6 +18512,56 @@ ], "time": "2023-09-08T10:17:14+00:00" }, + { + "name": "theseer/tokenizer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "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.2.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2021-07-28T10:34:58+00:00" + }, { "name": "vimeo/psalm", "version": "5.15.0", diff --git a/symfony.lock b/symfony.lock index 4948e590..3831aa60 100644 --- a/symfony.lock +++ b/symfony.lock @@ -365,6 +365,20 @@ "phpstan/phpstan-symfony": { "version": "0.12.4" }, + "phpunit/phpunit": { + "version": "9.6", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "9.6", + "ref": "7364a21d87e658eb363c5020c072ecfdc12e2326" + }, + "files": [ + "./.env.test", + "./phpunit.xml.dist", + "./tests/bootstrap.php" + ] + }, "psalm/plugin-symfony": { "version": "v1.2.1" }, From 1e52ec42cada166cf6e747dca4cb1ddf8d152d2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 1 Oct 2023 21:56:05 +0200 Subject: [PATCH 47/62] Added tests for availability of the API documentation --- tests/API/APIDocsAvailabilityTest.php | 71 +++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 tests/API/APIDocsAvailabilityTest.php diff --git a/tests/API/APIDocsAvailabilityTest.php b/tests/API/APIDocsAvailabilityTest.php new file mode 100644 index 00000000..053950e9 --- /dev/null +++ b/tests/API/APIDocsAvailabilityTest.php @@ -0,0 +1,71 @@ +. + */ + +declare(strict_types=1); + + +namespace API; + +use App\Entity\UserSystem\User; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; + +class APIDocsAvailabilityTest extends WebTestCase +{ + /** + * @dataProvider urlProvider + */ + public function testDocAvailabilityForLoggedInUser(string $url): void + { + self::ensureKernelShutdown(); + $client = static::createClient(); + $user = static::getContainer()->get(EntityManagerInterface::class) + ->getRepository(User::class)->findOneBy(['name' => 'admin']); + $client->loginUser($user); + + $client->request('GET',$url); + self::assertResponseIsSuccessful(); + } + + /** + * @dataProvider urlProvider + */ + public function testDocForbidden(string $url): void + { + self::ensureKernelShutdown(); + $client = static::createClient(); + $user = static::getContainer()->get(EntityManagerInterface::class) + ->getRepository(User::class)->findOneBy(['name' => 'noread']); + $client->loginUser($user); + + $client->request('GET',$url); + self::assertResponseStatusCodeSame(403); + } + + public static function urlProvider(): array + { + return [ + ['/api'], + ['/api/docs.html'], + ['/api/docs.json'], + //['/api/docs.jsonld'], + ]; + } +} \ No newline at end of file From 90518056cd793a83c068b11f5d2aa49f6279e41f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 1 Oct 2023 22:45:39 +0200 Subject: [PATCH 48/62] Fixed JSONLD schema generation of API documentation --- docs/api/intro.md | 2 + .../FixInheritanceMappingMetadataFacory.php | 72 +++++++++++++++++++ tests/API/APIDocsAvailabilityTest.php | 2 +- 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 src/ApiPlatform/FixInheritanceMappingMetadataFacory.php diff --git a/docs/api/intro.md b/docs/api/intro.md index 44b0db19..ddb37a44 100644 --- a/docs/api/intro.md +++ b/docs/api/intro.md @@ -67,6 +67,8 @@ This schema is a machine readable description of the API, which can be imported API generators which can generate a client library for the API from the schema are available for many programming languages, like [OpenAPI Generator](https://openapi-generator.tech/). +An JSONLD/Hydra version of the schema is also available under `/api/docs.jsonld` (so `https://your-part-db.local/api/docs.jsonld`). + ## Interactive documentation Part-DB provides an interactive documentation for the API, which is available under `/api/docs` (so `https://your-part-db.local/api/docs`). diff --git a/src/ApiPlatform/FixInheritanceMappingMetadataFacory.php b/src/ApiPlatform/FixInheritanceMappingMetadataFacory.php new file mode 100644 index 00000000..936a4bc5 --- /dev/null +++ b/src/ApiPlatform/FixInheritanceMappingMetadataFacory.php @@ -0,0 +1,72 @@ +. + */ + +declare(strict_types=1); + + +namespace App\ApiPlatform; + +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; +use App\Entity\Attachments\Attachment; +use App\Entity\Parameters\AbstractParameter; +use Symfony\Component\DependencyInjection\Attribute\AsDecorator; + +/** + * API Platform has problems with single table inheritance, as it assumes that they all have different endpoints. + * This decorator fixes this problem by using the parent class for the metadata collection. + */ +#[AsDecorator('api_platform.metadata.resource.metadata_collection_factory')] +class FixInheritanceMappingMetadataFacory implements ResourceMetadataCollectionFactoryInterface +{ + private const SINGLE_INHERITANCE_ENTITY_CLASSES = [ + Attachment::class, + AbstractParameter::class, + ]; + + private array $cache = []; + + public function __construct(private readonly ResourceMetadataCollectionFactoryInterface $decorated) + { + } + + public function create(string $resourceClass): ResourceMetadataCollection + { + //If we already have a cached value, we can return it + if (isset($this->cache[$resourceClass])) { + return $this->cache[$resourceClass]; + } + + //Check if the resourceClass is a single inheritance class, then we can use the parent class to access it + foreach (self::SINGLE_INHERITANCE_ENTITY_CLASSES as $class) { + if (is_a($resourceClass, $class, true)) { + $this->cache[$resourceClass] = $class; + break; + } + } + + //If it was not found in the list of single inheritance classes, we can use the original class + if (!isset($this->cache[$resourceClass])) { + $this->cache[$resourceClass] = $resourceClass; + } + + return $this->decorated->create($this->cache[$resourceClass] ?? $resourceClass); + } +} \ No newline at end of file diff --git a/tests/API/APIDocsAvailabilityTest.php b/tests/API/APIDocsAvailabilityTest.php index 053950e9..0b38a9b9 100644 --- a/tests/API/APIDocsAvailabilityTest.php +++ b/tests/API/APIDocsAvailabilityTest.php @@ -65,7 +65,7 @@ class APIDocsAvailabilityTest extends WebTestCase ['/api'], ['/api/docs.html'], ['/api/docs.json'], - //['/api/docs.jsonld'], + ['/api/docs.jsonld'], ]; } } \ No newline at end of file From 607bb45f5fbf4a364cfd04190733677dc4bd6036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 2 Oct 2023 00:21:59 +0200 Subject: [PATCH 49/62] Added an possibilty to document "virtual" properties to api documentation, which are added dynamically during the normalization priocess --- ...cumentedAPIPropertiesJSONSchemaFactory.php | 142 ++++++++++++++++++ src/ApiPlatform/DocumentedAPIProperty.php | 67 +++++++++ 2 files changed, 209 insertions(+) create mode 100644 src/ApiPlatform/AddDocumentedAPIPropertiesJSONSchemaFactory.php create mode 100644 src/ApiPlatform/DocumentedAPIProperty.php diff --git a/src/ApiPlatform/AddDocumentedAPIPropertiesJSONSchemaFactory.php b/src/ApiPlatform/AddDocumentedAPIPropertiesJSONSchemaFactory.php new file mode 100644 index 00000000..98b92a41 --- /dev/null +++ b/src/ApiPlatform/AddDocumentedAPIPropertiesJSONSchemaFactory.php @@ -0,0 +1,142 @@ +. + */ + +declare(strict_types=1); + + +namespace App\ApiPlatform; + +use ApiPlatform\JsonSchema\Schema; +use ApiPlatform\JsonSchema\SchemaFactoryInterface; +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\Operation; +use App\Entity\Attachments\Attachment; +use Symfony\Component\DependencyInjection\Attribute\AsDecorator; +use Symfony\Component\PropertyInfo\Type; + +/** + * This decorator adds the properties given by DocumentedAPIProperty attributes on the classes to the schema. + */ +#[AsDecorator('api_platform.json_schema.schema_factory')] +class AddDocumentedAPIPropertiesJSONSchemaFactory implements SchemaFactoryInterface +{ + + public function __construct(private readonly SchemaFactoryInterface $decorated) + { + } + + public function buildSchema( + string $className, + string $format = 'json', + string $type = Schema::TYPE_OUTPUT, + Operation $operation = null, + Schema $schema = null, + array $serializerContext = null, + bool $forceCollection = false + ): Schema { + + + $schema = $this->decorated->buildSchema($className, $format, $type, $operation, $schema, $serializerContext, $forceCollection); + + //Check if there is are DocumentedAPIProperty attributes on the class + $reflectionClass = new \ReflectionClass($className); + $attributes = $reflectionClass->getAttributes(DocumentedAPIProperty::class); + foreach ($attributes as $attribute) { + /** @var DocumentedAPIProperty $api_property */ + $api_property = $attribute->newInstance(); + $this->addPropertyToSchema($schema, $api_property->schemaName, $api_property->property, + $api_property, $serializerContext ?? [], $format); + } + + /*if ($className === Attachment::class) { + $api_property = new ApiProperty(description: 'Test'); + $this->buildPropertySchema($schema, 'Attachment-Read', 'media_url', $api_property, $serializerContext ?? [], + $format); + }*/ + + //Add media_url and thumbnail_url to the Attachment schema + /*if ($className === Attachment::class) { + $tmp = $schema->getDefinitions()->getArrayCopy(); + $tmp['properties']['media_url'] = [ + 'type' => 'string', + 'readOnly' => true, + 'format' => 'uri', + 'description' => 'The URL to the attachment', + ]; + $tmp['properties']['thumbnail_url'] = [ + 'type' => 'string', + 'readOnly' => true, + 'format' => 'uri', + 'description' => 'The URL to the thumbnail of the attachment', + ]; + $schema->setDefinitions(new \ArrayObject($tmp)); + }*/ + + //Fd + return $schema; + } + + private function addPropertyToSchema(Schema $schema, string $definitionName, string $normalizedPropertyName, DocumentedAPIProperty $propertyMetadata, array $serializerContext, string $format): void + { + $version = $schema->getVersion(); + $swagger = Schema::VERSION_SWAGGER === $version; + + if (false === $propertyMetadata->writeable) { + $propertySchema['readOnly'] = true; + } + if (!$swagger && false === $propertyMetadata->readable) { + $propertySchema['writeOnly'] = true; + } + if (null !== $description = $propertyMetadata->description) { + $propertySchema['description'] = $description; + } + + $deprecationReason = $propertyMetadata->deprecationReason; + + // see https://github.com/json-schema-org/json-schema-spec/pull/737 + if (!$swagger && null !== $deprecationReason) { + $propertySchema['deprecated'] = true; + } + + if (!isset($propertySchema['default']) && !empty($default = $propertyMetadata->default)) { + if ($default instanceof \BackedEnum) { + $default = $default->value; + } + $propertySchema['default'] = $default; + } + + if (!isset($propertySchema['example']) && !empty($example = $propertyMetadata->example)) { + $propertySchema['example'] = $example; + } + + if (!isset($propertySchema['example']) && isset($propertySchema['default'])) { + $propertySchema['example'] = $propertySchema['default']; + } + + $propertySchema['type'] = $propertyMetadata->type; + $propertySchema['nullable'] = $propertyMetadata->nullable; + + $propertySchema = new \ArrayObject($propertySchema); + + $schema->getDefinitions()[$definitionName]['properties'][$normalizedPropertyName] = $propertySchema; + } + + +} \ No newline at end of file diff --git a/src/ApiPlatform/DocumentedAPIProperty.php b/src/ApiPlatform/DocumentedAPIProperty.php new file mode 100644 index 00000000..c4c0a337 --- /dev/null +++ b/src/ApiPlatform/DocumentedAPIProperty.php @@ -0,0 +1,67 @@ +. + */ + +declare(strict_types=1); + + +namespace App\ApiPlatform; + +/** + * When this attribute is applied to a class, an property will be added to the API documentation using the given parameters. + * This is useful for adding properties to the API documentation, that are not existing in the entity class itself, + * but get added by a normalizer. + */ +#[\Attribute(\Attribute::TARGET_CLASS| \Attribute::IS_REPEATABLE)] +final class DocumentedAPIProperty +{ + public function __construct( + /** + * @param string $schemaName The name of the schema to add the property to (e.g. "Part-Read") + */ + public readonly string $schemaName, + /** + * @var string $property The name of the property to add to the schema + */ + public readonly string $property, + public readonly string $type = 'string', + public readonly bool $nullable = true, + /** + * @var string $description The description of the property + */ + public readonly ?string $description = null, + /** + * @var bool True if the property is readable, false otherwise + */ + public readonly bool $readable = true, + /** + * @var bool True if the property is writable, false otherwise + */ + public readonly bool $writeable = false, + /** + * @var string|null The deprecation reason of the property + */ + public readonly ?string $deprecationReason = null, + /** @var mixed The default value of this property */ + public readonly mixed $default = null, + public readonly mixed $example = null, + ) + { + } +} \ No newline at end of file From 7bd44484bec30b0f65dd43bde85ed6ab34ecec69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 2 Oct 2023 00:22:15 +0200 Subject: [PATCH 50/62] Added documentation for virtual fields of Attachment and Part entity --- src/Entity/Attachments/Attachment.php | 6 ++++++ src/Entity/Parts/Part.php | 3 +++ 2 files changed, 9 insertions(+) diff --git a/src/Entity/Attachments/Attachment.php b/src/Entity/Attachments/Attachment.php index 84d0260d..19810cc4 100644 --- a/src/Entity/Attachments/Attachment.php +++ b/src/Entity/Attachments/Attachment.php @@ -29,6 +29,7 @@ use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; +use App\ApiPlatform\DocumentedAPIProperty; use App\Repository\AttachmentRepository; use App\EntityListeners\AttachmentDeleteListener; use Doctrine\DBAL\Types\Types; @@ -73,6 +74,11 @@ use LogicException; normalizationContext: ['groups' => ['attachment:read', 'attachment:read:standalone', 'api:basic:read'], 'openapi_definition_name' => 'Read'], denormalizationContext: ['groups' => ['attachment:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], )] +#[DocumentedAPIProperty(schemaName: 'Attachment-Read', property: 'media_url', type: 'string', nullable: true, + description: 'The URL to the file, where the attachment file can be downloaded. This can be an internal or external URL.', + example: '/media/part/2/bc547-6508afa5a79c8.pdf')] +#[DocumentedAPIProperty(schemaName: 'Attachment-Read', property: 'thumbnail_url', type: 'string', nullable: true, + description: 'The URL to a thumbnail version of this file. This only exists for internal picture attachments.')] abstract class Attachment extends AbstractNamedDBElement { /** diff --git a/src/Entity/Parts/Part.php b/src/Entity/Parts/Part.php index 56e6638b..36a5d989 100644 --- a/src/Entity/Parts/Part.php +++ b/src/Entity/Parts/Part.php @@ -32,6 +32,7 @@ use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\DocumentedAPIProperty; use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\PartRepository; use Doctrine\DBAL\Types\Types; @@ -85,6 +86,8 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; denormalizationContext: ['groups' => ['part:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], )] #[ApiFilter(PropertyFilter::class)] +#[DocumentedAPIProperty(schemaName: 'Part-Read', property: 'total_instock', type: 'number', nullable: false, + description: 'The total amount of this part in stock (sum of all part lots).')] class Part extends AttachmentContainingDBElement { use AdvancedPropertyTrait; From b796ae36dbed862012d0f39422f842d2997f4ee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 2 Oct 2023 00:35:11 +0200 Subject: [PATCH 51/62] Restrict access to users API endpoint --- src/Entity/UserSystem/User.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Entity/UserSystem/User.php b/src/Entity/UserSystem/User.php index 6ce36d90..e50a90ba 100644 --- a/src/Entity/UserSystem/User.php +++ b/src/Entity/UserSystem/User.php @@ -83,8 +83,10 @@ use Jbtronics\TFAWebauthn\Model\TwoFactorInterface as WebauthnTwoFactorInterface #[ApiResource( shortName: 'User', operations: [ - new Get(openapiContext: ['summary' => 'Get a specific user.']), - new GetCollection(openapiContext: ['summary' => 'Get all users defined in the system.']), + new Get(openapiContext: ['summary' => 'Get a specific user.'], + security: 'is_granted("read", object)'), + new GetCollection(openapiContext: ['summary' => 'Get all users defined in the system.'], + security: 'is_granted("@users.read")'), ], normalizationContext: ['groups' => ['user:read'], 'openapi_definition_name' => 'Read'], )] @@ -108,7 +110,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe protected ?\DateTimeInterface $lastModified = null; #[Groups(['user:read'])] - protected ?\DateTimeInterface $createdAt = null; + protected ?\DateTimeInterface $addedDate = null; /** * @var bool Determines if the user is disabled (user can not log in) @@ -258,7 +260,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe #[ORM\ManyToOne(targetEntity: UserAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] - #[Groups(['user:read', 'usser:write'])] + #[Groups(['user:read', 'user:write'])] protected ?Attachment $master_picture_attachment = null; /** @var \DateTimeInterface|null The time when the backup codes were generated From 42356cc2a39fb0240f4caef675c8e236c80fe8e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 2 Oct 2023 00:45:21 +0200 Subject: [PATCH 52/62] Show the last 5 characters of the token secrets on the user settings page to make identification of a token easier --- src/Entity/UserSystem/ApiToken.php | 9 + templates/users/_api_tokens.html.twig | 3 +- translations/messages.en.xlf | 2626 +++++++++++++------------ translations/security.en.xlf | 4 +- translations/validators.en.xlf | 62 +- 5 files changed, 1360 insertions(+), 1344 deletions(-) diff --git a/src/Entity/UserSystem/ApiToken.php b/src/Entity/UserSystem/ApiToken.php index b46c5f8b..88a0ad84 100644 --- a/src/Entity/UserSystem/ApiToken.php +++ b/src/Entity/UserSystem/ApiToken.php @@ -182,5 +182,14 @@ class ApiToken return $this; } + /** + * Returns the last 4 characters of the token secret, which can be used to identify the token. + * @return string + */ + public function getLastTokenChars(): string + { + return substr($this->token, -4); + } + } \ No newline at end of file diff --git a/templates/users/_api_tokens.html.twig b/templates/users/_api_tokens.html.twig index c84543b2..1fe4d401 100644 --- a/templates/users/_api_tokens.html.twig +++ b/templates/users/_api_tokens.html.twig @@ -44,7 +44,8 @@ {% for api_token in user.apiTokens %} {# @var api_token \App\Entity\UserSystem\ApiToken #} - {{ api_token.name }} + {{ api_token.name }}
+ {% trans%}api_token.ends_with{% endtrans%} ...{{ api_token.lastTokenChars }} {{ api_token.level.translationKey|trans }} {{ _self.format_date(api_token.validUntil) }} diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index f06f7a93..82a6de8a 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -7,7 +7,7 @@ Part-DB1\templates\AdminPages\AttachmentTypeAdmin.html.twig:4 templates\AdminPages\AttachmentTypeAdmin.html.twig:4 - + attachment_type.caption File types for attachments @@ -17,7 +17,7 @@ Part-DB1\templates\AdminPages\AttachmentTypeAdmin.html.twig:12 new - + attachment_type.edit Edit file type @@ -27,7 +27,7 @@ Part-DB1\templates\AdminPages\AttachmentTypeAdmin.html.twig:16 new - + attachment_type.new New file type @@ -46,7 +46,7 @@ templates\base.html.twig:197 templates\base.html.twig:225 - + category.labelp Categories @@ -59,7 +59,7 @@ Part-DB1\templates\AdminPages\StorelocationAdmin.html.twig:11 templates\AdminPages\CategoryAdmin.html.twig:8 - + admin.options Options @@ -72,7 +72,7 @@ Part-DB1\templates\AdminPages\CompanyAdminBase.html.twig:15 templates\AdminPages\CategoryAdmin.html.twig:9 - + admin.advanced Advanced @@ -82,7 +82,7 @@ Part-DB1\templates\AdminPages\CategoryAdmin.html.twig:13 new - + category.edit Edit category @@ -92,7 +92,7 @@ Part-DB1\templates\AdminPages\CategoryAdmin.html.twig:17 new - + category.new New category @@ -102,7 +102,7 @@ Part-DB1\templates\AdminPages\CurrencyAdmin.html.twig:4 Part-DB1\templates\AdminPages\CurrencyAdmin.html.twig:4 - + currency.caption Currency @@ -112,7 +112,7 @@ Part-DB1\templates\AdminPages\CurrencyAdmin.html.twig:12 Part-DB1\templates\AdminPages\CurrencyAdmin.html.twig:12 - + currency.iso_code.caption ISO code @@ -122,7 +122,7 @@ Part-DB1\templates\AdminPages\CurrencyAdmin.html.twig:15 Part-DB1\templates\AdminPages\CurrencyAdmin.html.twig:15 - + currency.symbol.caption Currency symbol @@ -132,7 +132,7 @@ Part-DB1\templates\AdminPages\CurrencyAdmin.html.twig:29 new - + currency.edit Edit currency @@ -142,7 +142,7 @@ Part-DB1\templates\AdminPages\CurrencyAdmin.html.twig:33 new - + currency.new New currency @@ -153,7 +153,7 @@ Part-DB1\templates\AdminPages\DeviceAdmin.html.twig:4 templates\AdminPages\DeviceAdmin.html.twig:4 - + project.caption Project @@ -163,7 +163,7 @@ Part-DB1\templates\AdminPages\DeviceAdmin.html.twig:8 new - + project.edit Edit project @@ -173,7 +173,7 @@ Part-DB1\templates\AdminPages\DeviceAdmin.html.twig:12 new - + project.new New project @@ -196,7 +196,7 @@ templates\base.html.twig:206 templates\base.html.twig:237 - + search.placeholder Search @@ -212,7 +212,7 @@ templates\base.html.twig:193 templates\base.html.twig:221 - + expandAll Expand All @@ -228,7 +228,7 @@ templates\base.html.twig:194 templates\base.html.twig:222 - + reduceAll Reduce All @@ -240,9 +240,9 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:54 Part-DB1\templates\Parts\info\_sidebar.html.twig:4 - + part.info.timetravel_hint - This is how the part appeared before %timestamp%. <i>Please note that this feature is experimental, so the info may not be correct.</i> + Please note that this feature is experimental, so the info may not be correct.]]> @@ -251,7 +251,7 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:60 templates\AdminPages\EntityAdminBase.html.twig:42 - + standard.label Properties @@ -262,7 +262,7 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:61 templates\AdminPages\EntityAdminBase.html.twig:43 - + infos.label Info @@ -273,7 +273,7 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:63 new - + history.label History @@ -284,7 +284,7 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:66 templates\AdminPages\EntityAdminBase.html.twig:45 - + export.label Export @@ -295,7 +295,7 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:68 templates\AdminPages\EntityAdminBase.html.twig:47 - + import_export.label Import / Export @@ -305,7 +305,7 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:69 Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:69 - + mass_creation.label Mass creation @@ -316,7 +316,7 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:82 templates\AdminPages\EntityAdminBase.html.twig:59 - + admin.common Common @@ -326,7 +326,7 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:86 Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:86 - + admin.attachments Attachments @@ -335,7 +335,7 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:90 - + admin.parameters Parameters @@ -346,7 +346,7 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:167 templates\AdminPages\EntityAdminBase.html.twig:142 - + export_all.label Export all elements @@ -356,7 +356,7 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:185 Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:173 - + mass_creation.help Each line will be interpreted as a name of an element, which will be created. You can create nested structures by indentations. @@ -367,7 +367,7 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:45 templates\AdminPages\EntityAdminBase.html.twig:35 - + edit.caption Edit element "%name" @@ -378,7 +378,7 @@ Part-DB1\templates\AdminPages\EntityAdminBase.html.twig:50 templates\AdminPages\EntityAdminBase.html.twig:37 - + new.caption New element @@ -393,7 +393,7 @@ templates\base.html.twig:199 templates\base.html.twig:227 - + footprint.labelp Footprints @@ -403,7 +403,7 @@ Part-DB1\templates\AdminPages\FootprintAdmin.html.twig:13 new - + footprint.edit Edit footprint @@ -413,7 +413,7 @@ Part-DB1\templates\AdminPages\FootprintAdmin.html.twig:17 new - + footprint.new New footprint @@ -423,7 +423,7 @@ Part-DB1\templates\AdminPages\GroupAdmin.html.twig:4 Part-DB1\templates\AdminPages\GroupAdmin.html.twig:4 - + group.edit.caption Groups @@ -435,7 +435,7 @@ Part-DB1\templates\AdminPages\GroupAdmin.html.twig:9 Part-DB1\templates\AdminPages\UserAdmin.html.twig:16 - + user.edit.permissions Permissions @@ -445,7 +445,7 @@ Part-DB1\templates\AdminPages\GroupAdmin.html.twig:24 new - + group.edit Edit group @@ -455,7 +455,7 @@ Part-DB1\templates\AdminPages\GroupAdmin.html.twig:28 new - + group.new New group @@ -464,7 +464,7 @@ Part-DB1\templates\AdminPages\LabelProfileAdmin.html.twig:4 - + label_profile.caption Label profiles @@ -473,7 +473,7 @@ Part-DB1\templates\AdminPages\LabelProfileAdmin.html.twig:8 - + label_profile.advanced Advanced @@ -482,7 +482,7 @@ Part-DB1\templates\AdminPages\LabelProfileAdmin.html.twig:9 - + label_profile.comment Notes @@ -492,7 +492,7 @@ Part-DB1\templates\AdminPages\LabelProfileAdmin.html.twig:55 new - + label_profile.edit Edit label profile @@ -502,7 +502,7 @@ Part-DB1\templates\AdminPages\LabelProfileAdmin.html.twig:59 new - + label_profile.new New label profile @@ -513,7 +513,7 @@ Part-DB1\templates\AdminPages\ManufacturerAdmin.html.twig:4 templates\AdminPages\ManufacturerAdmin.html.twig:4 - + manufacturer.caption Manufacturers @@ -523,7 +523,7 @@ Part-DB1\templates\AdminPages\ManufacturerAdmin.html.twig:8 new - + manufacturer.edit Edit manufacturer @@ -533,7 +533,7 @@ Part-DB1\templates\AdminPages\ManufacturerAdmin.html.twig:12 new - + manufacturer.new New manufacturer @@ -543,7 +543,7 @@ Part-DB1\templates\AdminPages\MeasurementUnitAdmin.html.twig:4 Part-DB1\templates\AdminPages\MeasurementUnitAdmin.html.twig:4 - + measurement_unit.caption Measurement Unit @@ -558,7 +558,7 @@ templates\base.html.twig:198 templates\base.html.twig:226 - + storelocation.labelp Storage locations @@ -568,7 +568,7 @@ Part-DB1\templates\AdminPages\StorelocationAdmin.html.twig:32 new - + storelocation.edit Edit storage location @@ -578,7 +578,7 @@ Part-DB1\templates\AdminPages\StorelocationAdmin.html.twig:36 new - + storelocation.new New storage location @@ -589,7 +589,7 @@ Part-DB1\templates\AdminPages\SupplierAdmin.html.twig:4 templates\AdminPages\SupplierAdmin.html.twig:4 - + supplier.caption Suppliers @@ -599,7 +599,7 @@ Part-DB1\templates\AdminPages\SupplierAdmin.html.twig:16 new - + supplier.edit Edit supplier @@ -609,7 +609,7 @@ Part-DB1\templates\AdminPages\SupplierAdmin.html.twig:20 new - + supplier.new New supplier @@ -619,7 +619,7 @@ Part-DB1\templates\AdminPages\UserAdmin.html.twig:8 Part-DB1\templates\AdminPages\UserAdmin.html.twig:8 - + user.edit.caption Users @@ -629,7 +629,7 @@ Part-DB1\templates\AdminPages\UserAdmin.html.twig:14 Part-DB1\templates\AdminPages\UserAdmin.html.twig:14 - + user.edit.configuration Configuration @@ -639,7 +639,7 @@ Part-DB1\templates\AdminPages\UserAdmin.html.twig:15 Part-DB1\templates\AdminPages\UserAdmin.html.twig:15 - + user.edit.password Password @@ -649,7 +649,7 @@ Part-DB1\templates\AdminPages\UserAdmin.html.twig:45 Part-DB1\templates\AdminPages\UserAdmin.html.twig:45 - + user.edit.tfa.caption Two-factor authentication @@ -659,7 +659,7 @@ Part-DB1\templates\AdminPages\UserAdmin.html.twig:47 Part-DB1\templates\AdminPages\UserAdmin.html.twig:47 - + user.edit.tfa.google_active Authenticator app active @@ -673,7 +673,7 @@ Part-DB1\templates\Users\backup_codes.html.twig:15 Part-DB1\templates\Users\_2fa_settings.html.twig:95 - + tfa_backup.remaining_tokens Remaining backup codes count @@ -687,7 +687,7 @@ Part-DB1\templates\Users\backup_codes.html.twig:17 Part-DB1\templates\Users\_2fa_settings.html.twig:96 - + tfa_backup.generation_date Generation date of the backup codes @@ -699,7 +699,7 @@ Part-DB1\templates\AdminPages\UserAdmin.html.twig:53 Part-DB1\templates\AdminPages\UserAdmin.html.twig:60 - + user.edit.tfa.disabled Method not enabled @@ -709,7 +709,7 @@ Part-DB1\templates\AdminPages\UserAdmin.html.twig:56 Part-DB1\templates\AdminPages\UserAdmin.html.twig:56 - + user.edit.tfa.u2f_keys_count Active security keys @@ -719,7 +719,7 @@ Part-DB1\templates\AdminPages\UserAdmin.html.twig:72 Part-DB1\templates\AdminPages\UserAdmin.html.twig:72 - + user.edit.tfa.disable_tfa_title Do you really want to proceed? @@ -729,7 +729,7 @@ Part-DB1\templates\AdminPages\UserAdmin.html.twig:72 Part-DB1\templates\AdminPages\UserAdmin.html.twig:72 - + user.edit.tfa.disable_tfa_message all active two-factor authentication methods of the user and delete the backup codes!
@@ -742,7 +742,7 @@ The user will have to set up all two-factor authentication methods again and pri Part-DB1\templates\AdminPages\UserAdmin.html.twig:73 Part-DB1\templates\AdminPages\UserAdmin.html.twig:73 - + user.edit.tfa.disable_tfa.btn Disable all two-factor authentication methods @@ -752,7 +752,7 @@ The user will have to set up all two-factor authentication methods again and pri Part-DB1\templates\AdminPages\UserAdmin.html.twig:85 new - + user.edit Edit user @@ -762,7 +762,7 @@ The user will have to set up all two-factor authentication methods again and pri Part-DB1\templates\AdminPages\UserAdmin.html.twig:89 new - + user.new New user @@ -775,7 +775,7 @@ The user will have to set up all two-factor authentication methods again and pri Part-DB1\templates\Parts\edit\_attachments.html.twig:4 Part-DB1\templates\Parts\info\_attachments_info.html.twig:63 - + attachment.delete Delete @@ -789,7 +789,7 @@ The user will have to set up all two-factor authentication methods again and pri Part-DB1\templates\Parts\edit\_attachments.html.twig:38 Part-DB1\src\DataTables\AttachmentDataTable.php:159 - + attachment.external External @@ -801,7 +801,7 @@ The user will have to set up all two-factor authentication methods again and pri Part-DB1\templates\AdminPages\_attachments.html.twig:47 Part-DB1\templates\Parts\edit\_attachments.html.twig:45 - + attachment.preview.alt Attachment thumbnail @@ -815,7 +815,7 @@ The user will have to set up all two-factor authentication methods again and pri Part-DB1\templates\Parts\edit\_attachments.html.twig:48 Part-DB1\templates\Parts\info\_attachments_info.html.twig:45 - + attachment.view View @@ -831,7 +831,7 @@ The user will have to set up all two-factor authentication methods again and pri Part-DB1\templates\Parts\info\_attachments_info.html.twig:38 Part-DB1\src\DataTables\AttachmentDataTable.php:166 - + attachment.file_not_found File not found @@ -843,7 +843,7 @@ The user will have to set up all two-factor authentication methods again and pri Part-DB1\templates\Parts\info\_attachments_info.html.twig:48 Part-DB1\templates\Parts\edit\_attachments.html.twig:62 - + attachment.secure Private attachment @@ -855,7 +855,7 @@ The user will have to set up all two-factor authentication methods again and pri Part-DB1\templates\AdminPages\_attachments.html.twig:77 Part-DB1\templates\Parts\edit\_attachments.html.twig:75 - + attachment.create Add attachment @@ -869,7 +869,7 @@ The user will have to set up all two-factor authentication methods again and pri Part-DB1\templates\Parts\edit\_attachments.html.twig:80 Part-DB1\templates\Parts\edit\_lots.html.twig:33 - + part_lot.edit.delete.confirm Do you really want to delete this stock? This can not be undone! @@ -880,7 +880,7 @@ The user will have to set up all two-factor authentication methods again and pri Part-DB1\templates\AdminPages\_delete_form.html.twig:2 templates\AdminPages\_delete_form.html.twig:2 - + entity.delete.confirm_title You really want to delete %name%? @@ -891,11 +891,11 @@ The user will have to set up all two-factor authentication methods again and pri Part-DB1\templates\AdminPages\_delete_form.html.twig:3 templates\AdminPages\_delete_form.html.twig:3 - + entity.delete.message - This can not be undone! -<br> -Sub elements will be moved upwards. + +Sub elements will be moved upwards.]]>
@@ -904,7 +904,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_delete_form.html.twig:11 templates\AdminPages\_delete_form.html.twig:9 - + entity.delete Delete element @@ -919,7 +919,7 @@ Sub elements will be moved upwards. Part-DB1\src\Form\Part\PartBaseType.php:267 new - + edit.log_comment Change comment @@ -930,7 +930,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_delete_form.html.twig:24 templates\AdminPages\_delete_form.html.twig:12 - + entity.delete.recursive Delete recursive (all sub elements) @@ -939,7 +939,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_duplicate.html.twig:3 - + entity.duplicate Duplicate element @@ -953,7 +953,7 @@ Sub elements will be moved upwards. templates\AdminPages\_export_form.html.twig:4 src\Form\ImportType.php:67 - + export.format File format @@ -964,7 +964,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_export_form.html.twig:16 templates\AdminPages\_export_form.html.twig:16 - + export.level Verbosity level @@ -975,7 +975,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_export_form.html.twig:19 templates\AdminPages\_export_form.html.twig:19 - + export.level.simple Simple @@ -986,7 +986,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_export_form.html.twig:20 templates\AdminPages\_export_form.html.twig:20 - + export.level.extended Extended @@ -997,7 +997,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_export_form.html.twig:21 templates\AdminPages\_export_form.html.twig:21 - + export.level.full Full @@ -1008,7 +1008,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_export_form.html.twig:31 templates\AdminPages\_export_form.html.twig:31 - + export.include_children Include children elements in export @@ -1019,7 +1019,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_export_form.html.twig:39 templates\AdminPages\_export_form.html.twig:39 - + export.btn Export @@ -1038,7 +1038,7 @@ Sub elements will be moved upwards. templates\Parts\edit_part_info.html.twig:12 templates\Parts\show_part_info.html.twig:11 - + id.label ID @@ -1062,7 +1062,7 @@ Sub elements will be moved upwards. templates\AdminPages\EntityAdminBase.html.twig:101 templates\Parts\show_part_info.html.twig:248 - + createdAt Created At @@ -1080,7 +1080,7 @@ Sub elements will be moved upwards. templates\AdminPages\EntityAdminBase.html.twig:114 templates\Parts\show_part_info.html.twig:263 - + lastModified Last modified @@ -1090,7 +1090,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_info.html.twig:38 Part-DB1\templates\AdminPages\_info.html.twig:38 - + entity.info.parts_count Number of parts with this element @@ -1101,7 +1101,7 @@ Sub elements will be moved upwards. Part-DB1\templates\helper.twig:125 Part-DB1\templates\Parts\edit\_specifications.html.twig:6 - + specifications.property Parameter @@ -1111,7 +1111,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_parameters.html.twig:7 Part-DB1\templates\Parts\edit\_specifications.html.twig:7 - + specifications.symbol Symbol @@ -1121,7 +1121,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_parameters.html.twig:8 Part-DB1\templates\Parts\edit\_specifications.html.twig:8 - + specifications.value_min Min. @@ -1131,7 +1131,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_parameters.html.twig:9 Part-DB1\templates\Parts\edit\_specifications.html.twig:9 - + specifications.value_typ Typ. @@ -1141,7 +1141,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_parameters.html.twig:10 Part-DB1\templates\Parts\edit\_specifications.html.twig:10 - + specifications.value_max Max. @@ -1151,7 +1151,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_parameters.html.twig:11 Part-DB1\templates\Parts\edit\_specifications.html.twig:11 - + specifications.unit Unit @@ -1161,7 +1161,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_parameters.html.twig:12 Part-DB1\templates\Parts\edit\_specifications.html.twig:12 - + specifications.text Text @@ -1171,7 +1171,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_parameters.html.twig:13 Part-DB1\templates\Parts\edit\_specifications.html.twig:13 - + specifications.group Group @@ -1181,7 +1181,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_parameters.html.twig:26 Part-DB1\templates\Parts\edit\_specifications.html.twig:26 - + specification.create New Parameter @@ -1191,7 +1191,7 @@ Sub elements will be moved upwards. Part-DB1\templates\AdminPages\_parameters.html.twig:31 Part-DB1\templates\Parts\edit\_specifications.html.twig:31 - + parameter.delete.confirm Do you really want to delete this parameter? @@ -1201,7 +1201,7 @@ Sub elements will be moved upwards. Part-DB1\templates\attachment_list.html.twig:3 Part-DB1\templates\attachment_list.html.twig:3 - + attachment.list.title Attachments list @@ -1215,7 +1215,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LogSystem\_log_table.html.twig:8 Part-DB1\templates\Parts\lists\_parts_list.html.twig:6 - + part_list.loading.caption Loading @@ -1229,7 +1229,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LogSystem\_log_table.html.twig:9 Part-DB1\templates\Parts\lists\_parts_list.html.twig:7 - + part_list.loading.message This can take a moment. If this message do not disappear, try to reload the page. @@ -1240,7 +1240,7 @@ Sub elements will be moved upwards. Part-DB1\templates\base.html.twig:68 templates\base.html.twig:246 - + vendor.base.javascript_hint Please activate Javascript to use all features! @@ -1250,7 +1250,7 @@ Sub elements will be moved upwards. Part-DB1\templates\base.html.twig:73 Part-DB1\templates\base.html.twig:73 - + sidebar.big.toggle Show/Hide sidebar @@ -1261,7 +1261,7 @@ Sub elements will be moved upwards. Part-DB1\templates\base.html.twig:95 templates\base.html.twig:271 - + loading.caption Loading: @@ -1272,7 +1272,7 @@ Sub elements will be moved upwards. Part-DB1\templates\base.html.twig:96 templates\base.html.twig:272 - + loading.message This can take a while. If this messages stays for a long time, try to reload the page. @@ -1283,7 +1283,7 @@ Sub elements will be moved upwards. Part-DB1\templates\base.html.twig:101 templates\base.html.twig:277 - + loading.bar Loading... @@ -1294,7 +1294,7 @@ Sub elements will be moved upwards. Part-DB1\templates\base.html.twig:112 templates\base.html.twig:288 - + back_to_top Back to page's top @@ -1304,7 +1304,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Form\permissionLayout.html.twig:35 Part-DB1\templates\Form\permissionLayout.html.twig:35 - + permission.edit.permission Permissions @@ -1314,7 +1314,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Form\permissionLayout.html.twig:36 Part-DB1\templates\Form\permissionLayout.html.twig:36 - + permission.edit.value Value @@ -1324,7 +1324,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Form\permissionLayout.html.twig:53 Part-DB1\templates\Form\permissionLayout.html.twig:53 - + permission.legend.title Explanation of the states: @@ -1334,7 +1334,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Form\permissionLayout.html.twig:57 Part-DB1\templates\Form\permissionLayout.html.twig:57 - + permission.legend.disallow Forbidden @@ -1344,7 +1344,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Form\permissionLayout.html.twig:61 Part-DB1\templates\Form\permissionLayout.html.twig:61 - + permission.legend.allow Allowed @@ -1354,7 +1354,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Form\permissionLayout.html.twig:65 Part-DB1\templates\Form\permissionLayout.html.twig:65 - + permission.legend.inherit Inherit from (parent) group @@ -1364,7 +1364,7 @@ Sub elements will be moved upwards. Part-DB1\templates\helper.twig:3 Part-DB1\templates\helper.twig:3 - + bool.true True @@ -1374,7 +1374,7 @@ Sub elements will be moved upwards. Part-DB1\templates\helper.twig:5 Part-DB1\templates\helper.twig:5 - + bool.false False @@ -1384,7 +1384,7 @@ Sub elements will be moved upwards. Part-DB1\templates\helper.twig:92 Part-DB1\templates\helper.twig:87 - + Yes Yes @@ -1394,7 +1394,7 @@ Sub elements will be moved upwards. Part-DB1\templates\helper.twig:94 Part-DB1\templates\helper.twig:89 - + No No @@ -1403,7 +1403,7 @@ Sub elements will be moved upwards. Part-DB1\templates\helper.twig:126 - + specifications.value Value @@ -1414,7 +1414,7 @@ Sub elements will be moved upwards. Part-DB1\templates\homepage.html.twig:7 templates\homepage.html.twig:7 - + version.caption Version @@ -1425,7 +1425,7 @@ Sub elements will be moved upwards. Part-DB1\templates\homepage.html.twig:22 templates\homepage.html.twig:19 - + homepage.license License information @@ -1436,7 +1436,7 @@ Sub elements will be moved upwards. Part-DB1\templates\homepage.html.twig:31 templates\homepage.html.twig:28 - + homepage.github.caption Project page @@ -1447,9 +1447,9 @@ Sub elements will be moved upwards. Part-DB1\templates\homepage.html.twig:31 templates\homepage.html.twig:28 - + homepage.github.text - Source, downloads, bug reports, to-do-list etc. can be found on <a href="%href%" class="link-external" target="_blank">GitHub project page</a> + GitHub project page
]]> @@ -1458,7 +1458,7 @@ Sub elements will be moved upwards. Part-DB1\templates\homepage.html.twig:32 templates\homepage.html.twig:29 - + homepage.help.caption Help @@ -1469,9 +1469,9 @@ Sub elements will be moved upwards. Part-DB1\templates\homepage.html.twig:32 templates\homepage.html.twig:29 - + homepage.help.text - Help and tips can be found in Wiki the <a href="%href%" class="link-external" target="_blank">GitHub page</a> + GitHub page]]> @@ -1480,7 +1480,7 @@ Sub elements will be moved upwards. Part-DB1\templates\homepage.html.twig:33 templates\homepage.html.twig:30 - + homepage.forum.caption Forum @@ -1491,7 +1491,7 @@ Sub elements will be moved upwards. Part-DB1\templates\homepage.html.twig:45 new - + homepage.last_activity Last activity @@ -1501,7 +1501,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\dialog.html.twig:3 Part-DB1\templates\LabelSystem\dialog.html.twig:6 - + label_generator.title Label generator @@ -1510,7 +1510,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\dialog.html.twig:16 - + label_generator.common Common @@ -1519,7 +1519,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\dialog.html.twig:20 - + label_generator.advanced Advanced @@ -1528,7 +1528,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\dialog.html.twig:24 - + label_generator.profiles Profiles @@ -1537,7 +1537,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\dialog.html.twig:58 - + label_generator.selected_profile Currently selected profile @@ -1546,7 +1546,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\dialog.html.twig:62 - + label_generator.edit_profile Edit profile @@ -1555,7 +1555,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\dialog.html.twig:75 - + label_generator.load_profile Load profile @@ -1564,7 +1564,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\dialog.html.twig:102 - + label_generator.download Download @@ -1574,7 +1574,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\dropdown_macro.html.twig:3 Part-DB1\templates\LabelSystem\dropdown_macro.html.twig:5 - + label_generator.label_btn Generate label @@ -1583,7 +1583,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\dropdown_macro.html.twig:20 - + label_generator.label_empty New empty label @@ -1592,7 +1592,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\Scanner\dialog.html.twig:3 - + label_scanner.title Label scanner @@ -1601,7 +1601,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\Scanner\dialog.html.twig:7 - + label_scanner.no_cam_found.title No webcam found @@ -1610,7 +1610,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\Scanner\dialog.html.twig:7 - + label_scanner.no_cam_found.text You need a webcam and give permission to use the scanner function. You can input the barcode code manually below. @@ -1619,7 +1619,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LabelSystem\Scanner\dialog.html.twig:27 - + label_scanner.source_select Select source @@ -1629,7 +1629,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LogSystem\log_list.html.twig:3 Part-DB1\templates\LogSystem\log_list.html.twig:3 - + log.list.title System log @@ -1640,7 +1640,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LogSystem\_log_table.html.twig:1 new - + log.undo.confirm_title Really undo change / revert to timestamp? @@ -1651,7 +1651,7 @@ Sub elements will be moved upwards. Part-DB1\templates\LogSystem\_log_table.html.twig:2 new - + log.undo.confirm_message Do you really want to undo the given change / reset the element to the given timestamp? @@ -1661,7 +1661,7 @@ Sub elements will be moved upwards. Part-DB1\templates\mail\base.html.twig:24 Part-DB1\templates\mail\base.html.twig:24 - + mail.footer.email_sent_by This email was sent automatically by @@ -1671,7 +1671,7 @@ Sub elements will be moved upwards. Part-DB1\templates\mail\base.html.twig:24 Part-DB1\templates\mail\base.html.twig:24 - + mail.footer.dont_reply Do not answer to this email. @@ -1681,7 +1681,7 @@ Sub elements will be moved upwards. Part-DB1\templates\mail\pw_reset.html.twig:6 Part-DB1\templates\mail\pw_reset.html.twig:6 - + email.hi %name% Hi %name% @@ -1691,7 +1691,7 @@ Sub elements will be moved upwards. Part-DB1\templates\mail\pw_reset.html.twig:7 Part-DB1\templates\mail\pw_reset.html.twig:7 - + email.pw_reset.message somebody (hopefully you) requested a reset of your password. If this request was not made by you, ignore this mail. @@ -1701,7 +1701,7 @@ Sub elements will be moved upwards. Part-DB1\templates\mail\pw_reset.html.twig:9 Part-DB1\templates\mail\pw_reset.html.twig:9 - + email.pw_reset.button Click here to reset password @@ -1711,9 +1711,9 @@ Sub elements will be moved upwards. Part-DB1\templates\mail\pw_reset.html.twig:11 Part-DB1\templates\mail\pw_reset.html.twig:11 - + email.pw_reset.fallback - If this does not work for you, go to <a href="%url%">%url%</a> and enter the following info + %url% and enter the following info]]> @@ -1721,7 +1721,7 @@ Sub elements will be moved upwards. Part-DB1\templates\mail\pw_reset.html.twig:16 Part-DB1\templates\mail\pw_reset.html.twig:16 - + email.pw_reset.username Username @@ -1731,7 +1731,7 @@ Sub elements will be moved upwards. Part-DB1\templates\mail\pw_reset.html.twig:19 Part-DB1\templates\mail\pw_reset.html.twig:19 - + email.pw_reset.token Token @@ -1741,9 +1741,9 @@ Sub elements will be moved upwards. Part-DB1\templates\mail\pw_reset.html.twig:24 Part-DB1\templates\mail\pw_reset.html.twig:24 - + email.pw_reset.valid_unit %date% - The reset token will be valid until <i>%date%</i>. + %date%.]]> @@ -1753,7 +1753,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:78 Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:58 - + orderdetail.delete Delete @@ -1763,7 +1763,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:39 Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:39 - + pricedetails.edit.min_qty Minimum discount quantity @@ -1773,7 +1773,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:40 Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:40 - + pricedetails.edit.price Price @@ -1783,7 +1783,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:41 Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:41 - + pricedetails.edit.price_qty for amount @@ -1793,7 +1793,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:54 Part-DB1\templates\Parts\edit\edit_form_styles.html.twig:54 - + pricedetail.create Add price @@ -1804,7 +1804,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_part_info.html.twig:4 templates\Parts\edit_part_info.html.twig:4 - + part.edit.title Edit part @@ -1815,7 +1815,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_part_info.html.twig:9 templates\Parts\edit_part_info.html.twig:9 - + part.edit.card_title Edit part @@ -1825,7 +1825,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_part_info.html.twig:22 Part-DB1\templates\Parts\edit\edit_part_info.html.twig:22 - + part.edit.tab.common Common @@ -1835,7 +1835,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_part_info.html.twig:28 Part-DB1\templates\Parts\edit\edit_part_info.html.twig:28 - + part.edit.tab.manufacturer Manufacturer @@ -1845,7 +1845,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_part_info.html.twig:34 Part-DB1\templates\Parts\edit\edit_part_info.html.twig:34 - + part.edit.tab.advanced Advanced @@ -1855,7 +1855,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_part_info.html.twig:40 Part-DB1\templates\Parts\edit\edit_part_info.html.twig:40 - + part.edit.tab.part_lots Stocks @@ -1865,7 +1865,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_part_info.html.twig:46 Part-DB1\templates\Parts\edit\edit_part_info.html.twig:46 - + part.edit.tab.attachments Attachments @@ -1875,7 +1875,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_part_info.html.twig:52 Part-DB1\templates\Parts\edit\edit_part_info.html.twig:52 - + part.edit.tab.orderdetails Purchase information @@ -1884,7 +1884,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_part_info.html.twig:58 - + part.edit.tab.specifications Parameters @@ -1894,7 +1894,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\edit_part_info.html.twig:64 Part-DB1\templates\Parts\edit\edit_part_info.html.twig:58 - + part.edit.tab.comment Notes @@ -1905,7 +1905,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\new_part.html.twig:8 templates\Parts\new_part.html.twig:8 - + part.new.card_title Create new part @@ -1915,7 +1915,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\_lots.html.twig:5 Part-DB1\templates\Parts\edit\_lots.html.twig:5 - + part_lot.delete Delete @@ -1925,7 +1925,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\_lots.html.twig:28 Part-DB1\templates\Parts\edit\_lots.html.twig:28 - + part_lot.create Add stock @@ -1935,7 +1935,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\_orderdetails.html.twig:13 Part-DB1\templates\Parts\edit\_orderdetails.html.twig:13 - + orderdetail.create Add distributor @@ -1945,7 +1945,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\_orderdetails.html.twig:18 Part-DB1\templates\Parts\edit\_orderdetails.html.twig:18 - + pricedetails.edit.delete.confirm Do you really want to delete this price? This can not be undone. @@ -1955,7 +1955,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\edit\_orderdetails.html.twig:62 Part-DB1\templates\Parts\edit\_orderdetails.html.twig:61 - + orderdetails.edit.delete.confirm Do you really want to delete this distributor info? This can not be undone! @@ -1969,7 +1969,7 @@ Sub elements will be moved upwards. templates\Parts\show_part_info.html.twig:4 templates\Parts\show_part_info.html.twig:9 - + part.info.title Detail info for part @@ -1979,7 +1979,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\show_part_info.html.twig:47 Part-DB1\templates\Parts\info\show_part_info.html.twig:47 - + part.part_lots.label Stocks @@ -1994,7 +1994,7 @@ Sub elements will be moved upwards. templates\Parts\show_part_info.html.twig:74 src\Form\PartType.php:86 - + comment.label Notes @@ -2003,7 +2003,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\show_part_info.html.twig:64 - + part.info.specifications Parameters @@ -2014,7 +2014,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\show_part_info.html.twig:64 templates\Parts\show_part_info.html.twig:82 - + attachment.labelp Attachments @@ -2025,7 +2025,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\show_part_info.html.twig:71 templates\Parts\show_part_info.html.twig:88 - + vendor.partinfo.shopping_infos Shopping information @@ -2036,7 +2036,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\show_part_info.html.twig:78 templates\Parts\show_part_info.html.twig:94 - + vendor.partinfo.history History @@ -2055,7 +2055,7 @@ Sub elements will be moved upwards. templates\base.html.twig:231 templates\Parts\show_part_info.html.twig:100 - + tools.label Tools @@ -2065,7 +2065,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\show_part_info.html.twig:103 Part-DB1\templates\Parts\info\show_part_info.html.twig:90 - + extended_info.label Extended info @@ -2075,7 +2075,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_attachments_info.html.twig:7 Part-DB1\templates\Parts\info\_attachments_info.html.twig:7 - + attachment.name Name @@ -2085,7 +2085,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_attachments_info.html.twig:8 Part-DB1\templates\Parts\info\_attachments_info.html.twig:8 - + attachment.attachment_type Attachment Type @@ -2095,7 +2095,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_attachments_info.html.twig:9 Part-DB1\templates\Parts\info\_attachments_info.html.twig:9 - + attachment.file_name File name @@ -2105,7 +2105,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_attachments_info.html.twig:10 Part-DB1\templates\Parts\info\_attachments_info.html.twig:10 - + attachment.file_size File size @@ -2114,7 +2114,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_attachments_info.html.twig:54 - + attachment.preview Preview picture @@ -2124,7 +2124,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_attachments_info.html.twig:67 Part-DB1\templates\Parts\info\_attachments_info.html.twig:50 - + attachment.download Download @@ -2135,7 +2135,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_extended_infos.html.twig:11 new - + user.creating_user User who created this part @@ -2149,7 +2149,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_extended_infos.html.twig:28 Part-DB1\templates\Parts\info\_extended_infos.html.twig:50 - + Unknown Unknown @@ -2162,7 +2162,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_extended_infos.html.twig:30 new - + accessDenied Access Denied @@ -2173,7 +2173,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_extended_infos.html.twig:26 new - + user.last_editing_user User who edited this part last @@ -2183,7 +2183,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_extended_infos.html.twig:41 Part-DB1\templates\Parts\info\_extended_infos.html.twig:41 - + part.isFavorite Favorite @@ -2193,7 +2193,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_extended_infos.html.twig:46 Part-DB1\templates\Parts\info\_extended_infos.html.twig:46 - + part.minOrderAmount Minimum order amount @@ -2210,7 +2210,7 @@ Sub elements will be moved upwards. templates\Parts\show_part_info.html.twig:24 src\Form\PartType.php:80 - + manufacturer.label Manufacturer @@ -2222,7 +2222,7 @@ Sub elements will be moved upwards. templates\base.html.twig:54 src\Form\PartType.php:62 - + name.label Name @@ -2233,7 +2233,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_main_infos.html.twig:27 new - + part.back_to_info Back to current version @@ -2248,7 +2248,7 @@ Sub elements will be moved upwards. templates\Parts\show_part_info.html.twig:31 src\Form\PartType.php:65 - + description.label Description @@ -2265,7 +2265,7 @@ Sub elements will be moved upwards. templates\Parts\show_part_info.html.twig:32 src\Form\PartType.php:74 - + category.label Category @@ -2277,7 +2277,7 @@ Sub elements will be moved upwards. templates\Parts\show_part_info.html.twig:42 src\Form\PartType.php:69 - + instock.label Instock @@ -2289,7 +2289,7 @@ Sub elements will be moved upwards. templates\Parts\show_part_info.html.twig:44 src\Form\PartType.php:72 - + mininstock.label Minimum Instock @@ -2305,7 +2305,7 @@ Sub elements will be moved upwards. templates\base.html.twig:73 templates\Parts\show_part_info.html.twig:47 - + footprint.label Footprint @@ -2318,7 +2318,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_main_infos.html.twig:60 templates\Parts\show_part_info.html.twig:51 - + part.avg_price.label Average Price @@ -2328,7 +2328,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_order_infos.html.twig:5 Part-DB1\templates\Parts\info\_order_infos.html.twig:5 - + part.supplier.name Name @@ -2338,7 +2338,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_order_infos.html.twig:6 Part-DB1\templates\Parts\info\_order_infos.html.twig:6 - + part.supplier.partnr Partnr. @@ -2348,7 +2348,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_order_infos.html.twig:28 Part-DB1\templates\Parts\info\_order_infos.html.twig:28 - + part.order.minamount Minimum amount @@ -2358,7 +2358,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_order_infos.html.twig:29 Part-DB1\templates\Parts\info\_order_infos.html.twig:29 - + part.order.price Price @@ -2368,7 +2368,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_order_infos.html.twig:31 Part-DB1\templates\Parts\info\_order_infos.html.twig:31 - + part.order.single_price Unit Price @@ -2378,7 +2378,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_order_infos.html.twig:71 Part-DB1\templates\Parts\info\_order_infos.html.twig:71 - + edit.caption_short Edit @@ -2388,7 +2388,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_order_infos.html.twig:72 Part-DB1\templates\Parts\info\_order_infos.html.twig:72 - + delete.caption Delete @@ -2398,7 +2398,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_part_lots.html.twig:7 Part-DB1\templates\Parts\info\_part_lots.html.twig:6 - + part_lots.description Description @@ -2408,7 +2408,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_part_lots.html.twig:8 Part-DB1\templates\Parts\info\_part_lots.html.twig:7 - + part_lots.storage_location Storage location @@ -2418,7 +2418,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_part_lots.html.twig:9 Part-DB1\templates\Parts\info\_part_lots.html.twig:8 - + part_lots.amount Amount @@ -2428,7 +2428,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_part_lots.html.twig:24 Part-DB1\templates\Parts\info\_part_lots.html.twig:22 - + part_lots.location_unknown Storage location unknown @@ -2438,7 +2438,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_part_lots.html.twig:31 Part-DB1\templates\Parts\info\_part_lots.html.twig:29 - + part_lots.instock_unknown Amount unknown @@ -2448,7 +2448,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_part_lots.html.twig:40 Part-DB1\templates\Parts\info\_part_lots.html.twig:38 - + part_lots.expiration_date Expiration date @@ -2458,7 +2458,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_part_lots.html.twig:48 Part-DB1\templates\Parts\info\_part_lots.html.twig:46 - + part_lots.is_expired Expired @@ -2468,7 +2468,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_part_lots.html.twig:55 Part-DB1\templates\Parts\info\_part_lots.html.twig:53 - + part_lots.need_refill Needs refill @@ -2478,7 +2478,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_picture.html.twig:15 Part-DB1\templates\Parts\info\_picture.html.twig:15 - + part.info.prev_picture Previous picture @@ -2488,7 +2488,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_picture.html.twig:19 Part-DB1\templates\Parts\info\_picture.html.twig:19 - + part.info.next_picture Next picture @@ -2498,7 +2498,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_sidebar.html.twig:21 Part-DB1\templates\Parts\info\_sidebar.html.twig:21 - + part.mass.tooltip Mass @@ -2508,7 +2508,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_sidebar.html.twig:30 Part-DB1\templates\Parts\info\_sidebar.html.twig:30 - + part.needs_review.badge Needs review @@ -2518,7 +2518,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_sidebar.html.twig:39 Part-DB1\templates\Parts\info\_sidebar.html.twig:39 - + part.favorite.badge Favorite @@ -2528,7 +2528,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_sidebar.html.twig:47 Part-DB1\templates\Parts\info\_sidebar.html.twig:47 - + part.obsolete.badge No longer available @@ -2537,7 +2537,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_specifications.html.twig:10 - + parameters.extracted_from_description Automatically extracted from description @@ -2546,7 +2546,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_specifications.html.twig:15 - + parameters.auto_extracted_from_comment Automatically extracted from notes @@ -2557,7 +2557,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_tools.html.twig:4 templates\Parts\show_part_info.html.twig:125 - + part.edit.btn Edit part @@ -2568,7 +2568,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_tools.html.twig:14 templates\Parts\show_part_info.html.twig:135 - + part.clone.btn Clone part @@ -2579,7 +2579,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\_action_bar.html.twig:4 templates\Parts\show_part_info.html.twig:143 - + part.create.btn Create new part @@ -2589,7 +2589,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_tools.html.twig:31 Part-DB1\templates\Parts\info\_tools.html.twig:29 - + part.delete.confirm_title Do you really want to delete this part? @@ -2599,7 +2599,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_tools.html.twig:32 Part-DB1\templates\Parts\info\_tools.html.twig:30 - + part.delete.message This part and any associated information (like attachments, price information, etc.) will be deleted. This can not be undone! @@ -2609,7 +2609,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\info\_tools.html.twig:39 Part-DB1\templates\Parts\info\_tools.html.twig:37 - + part.delete Delete part @@ -2619,7 +2619,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\all_list.html.twig:4 Part-DB1\templates\Parts\lists\all_list.html.twig:4 - + parts_list.all.title All parts @@ -2629,7 +2629,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\category_list.html.twig:4 Part-DB1\templates\Parts\lists\category_list.html.twig:4 - + parts_list.category.title Parts with category @@ -2639,7 +2639,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\footprint_list.html.twig:4 Part-DB1\templates\Parts\lists\footprint_list.html.twig:4 - + parts_list.footprint.title Parts with footprint @@ -2649,7 +2649,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\manufacturer_list.html.twig:4 Part-DB1\templates\Parts\lists\manufacturer_list.html.twig:4 - + parts_list.manufacturer.title Parts with manufacturer @@ -2659,7 +2659,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\search_list.html.twig:4 Part-DB1\templates\Parts\lists\search_list.html.twig:4 - + parts_list.search.title Search Parts @@ -2669,7 +2669,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\store_location_list.html.twig:4 Part-DB1\templates\Parts\lists\store_location_list.html.twig:4 - + parts_list.storelocation.title Parts with storage locations @@ -2679,7 +2679,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\supplier_list.html.twig:4 Part-DB1\templates\Parts\lists\supplier_list.html.twig:4 - + parts_list.supplier.title Parts with supplier @@ -2689,7 +2689,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\tags_list.html.twig:4 Part-DB1\templates\Parts\lists\tags_list.html.twig:4 - + parts_list.tags.title Parts with tag @@ -2699,7 +2699,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\_info_card.html.twig:22 Part-DB1\templates\Parts\lists\_info_card.html.twig:17 - + entity.info.common.tab Common @@ -2709,7 +2709,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\_info_card.html.twig:26 Part-DB1\templates\Parts\lists\_info_card.html.twig:20 - + entity.info.statistics.tab Statistics @@ -2718,7 +2718,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\_info_card.html.twig:31 - + entity.info.attachments.tab Attachments @@ -2727,7 +2727,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\_info_card.html.twig:37 - + entity.info.parameters.tab Parameters @@ -2737,7 +2737,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\_info_card.html.twig:54 Part-DB1\templates\Parts\lists\_info_card.html.twig:30 - + entity.info.name Name @@ -2749,7 +2749,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\_info_card.html.twig:34 Part-DB1\templates\Parts\lists\_info_card.html.twig:67 - + entity.info.parent Parent @@ -2759,7 +2759,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\_info_card.html.twig:70 Part-DB1\templates\Parts\lists\_info_card.html.twig:46 - + entity.edit.btn Edit @@ -2769,7 +2769,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Parts\lists\_info_card.html.twig:92 Part-DB1\templates\Parts\lists\_info_card.html.twig:63 - + entity.info.children_count Count of children elements @@ -2781,7 +2781,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\2fa_base_form.html.twig:3 Part-DB1\templates\security\2fa_base_form.html.twig:5 - + tfa.check.title Two-factor authentication needed @@ -2791,7 +2791,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\2fa_base_form.html.twig:39 Part-DB1\templates\security\2fa_base_form.html.twig:39 - + tfa.code.trusted_pc This is a trusted computer (if this is enabled, no further two-factor queries are performed on this computer) @@ -2803,7 +2803,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\2fa_base_form.html.twig:52 Part-DB1\templates\security\login.html.twig:58 - + login.btn Login @@ -2817,7 +2817,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\U2F\u2f_login.html.twig:13 Part-DB1\templates\_navbar.html.twig:40 - + user.logout Logout @@ -2827,7 +2827,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\2fa_form.html.twig:6 Part-DB1\templates\security\2fa_form.html.twig:6 - + tfa.check.code.label Authenticator app code @@ -2837,7 +2837,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\2fa_form.html.twig:10 Part-DB1\templates\security\2fa_form.html.twig:10 - + tfa.check.code.help Enter the 6-digit code from your Authenticator App or one of your backup codes if the Authenticator is not available. @@ -2848,7 +2848,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\login.html.twig:3 templates\security\login.html.twig:3 - + login.title Login @@ -2859,7 +2859,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\login.html.twig:7 templates\security\login.html.twig:7 - + login.card_title Login @@ -2870,7 +2870,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\login.html.twig:31 templates\security\login.html.twig:31 - + login.username.label Username @@ -2881,7 +2881,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\login.html.twig:34 templates\security\login.html.twig:34 - + login.username.placeholder Username @@ -2892,7 +2892,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\login.html.twig:38 templates\security\login.html.twig:38 - + login.password.label Password @@ -2903,7 +2903,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\login.html.twig:40 templates\security\login.html.twig:40 - + login.password.placeholder Password @@ -2914,7 +2914,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\login.html.twig:50 templates\security\login.html.twig:50 - + login.rememberme Remember me (should not be used on shared computers) @@ -2924,7 +2924,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\login.html.twig:64 Part-DB1\templates\security\login.html.twig:64 - + pw_reset.password_forget Forgot username/password? @@ -2934,7 +2934,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\pw_reset_new_pw.html.twig:5 Part-DB1\templates\security\pw_reset_new_pw.html.twig:5 - + pw_reset.new_pw.header.title Set new password @@ -2944,7 +2944,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\pw_reset_request.html.twig:5 Part-DB1\templates\security\pw_reset_request.html.twig:5 - + pw_reset.request.header.title Request a new password @@ -2956,7 +2956,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\U2F\u2f_login.html.twig:7 Part-DB1\templates\security\U2F\u2f_register.html.twig:10 - + tfa_u2f.http_warning You are accessing this page using the insecure HTTP method, so U2F will most likely not work (Bad Request error message). Ask an administrator to set up the secure HTTPS method if you want to use security keys. @@ -2968,7 +2968,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\U2F\u2f_login.html.twig:10 Part-DB1\templates\security\U2F\u2f_register.html.twig:22 - + r_u2f_two_factor.pressbutton Please plug in your security key and press its button! @@ -2978,7 +2978,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\U2F\u2f_register.html.twig:3 Part-DB1\templates\security\U2F\u2f_register.html.twig:3 - + tfa_u2f.add_key.title Add security key @@ -2990,7 +2990,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\U2F\u2f_register.html.twig:6 Part-DB1\templates\Users\_2fa_settings.html.twig:111 - + tfa_u2f.explanation With the help of a U2F/FIDO compatible security key (e.g. YubiKey or NitroKey), user-friendly and secure two-factor authentication can be achieved. The security keys can be registered here, and if two-factor verification is required, the key only needs to be inserted via USB or typed against the device via NFC. @@ -3000,7 +3000,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\U2F\u2f_register.html.twig:7 Part-DB1\templates\security\U2F\u2f_register.html.twig:7 - + tfa_u2f.add_key.backup_hint To ensure access even if the key is lost, it is recommended to register a second key as backup and store it in a safe place! @@ -3010,7 +3010,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\U2F\u2f_register.html.twig:16 Part-DB1\templates\security\U2F\u2f_register.html.twig:16 - + r_u2f_two_factor.name Shown key name (e.g. Backup) @@ -3020,7 +3020,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\U2F\u2f_register.html.twig:19 Part-DB1\templates\security\U2F\u2f_register.html.twig:19 - + tfa_u2f.add_key.add_button Add security key @@ -3030,7 +3030,7 @@ Sub elements will be moved upwards. Part-DB1\templates\security\U2F\u2f_register.html.twig:27 Part-DB1\templates\security\U2F\u2f_register.html.twig:27 - + tfa_u2f.add_key.back_to_settings Back to settings @@ -3043,7 +3043,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:8 new - + statistics.title Statistics @@ -3054,7 +3054,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:14 new - + statistics.parts Parts @@ -3065,7 +3065,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:19 new - + statistics.data_structures Data structures @@ -3076,7 +3076,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:24 new - + statistics.attachments Attachments @@ -3091,7 +3091,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:104 new - + statistics.property Property @@ -3106,7 +3106,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:105 new - + statistics.value Value @@ -3117,7 +3117,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:40 new - + statistics.distinct_parts_count Number of distinct parts @@ -3128,7 +3128,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:44 new - + statistics.parts_instock_sum Sum of all part instocks @@ -3139,7 +3139,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:48 new - + statistics.parts_with_price Number of parts with price information @@ -3150,7 +3150,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:65 new - + statistics.categories_count Number of categories @@ -3161,7 +3161,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:69 new - + statistics.footprints_count Number of footprints @@ -3172,7 +3172,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:73 new - + statistics.manufacturers_count Number of manufacturers @@ -3183,7 +3183,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:77 new - + statistics.storelocations_count Number of storelocations @@ -3194,7 +3194,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:81 new - + statistics.suppliers_count Number of suppliers @@ -3205,7 +3205,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:85 new - + statistics.currencies_count Number of currencies @@ -3216,7 +3216,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:89 new - + statistics.measurement_units_count Number of measurement units @@ -3227,7 +3227,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:93 new - + statistics.devices_count Number of projects @@ -3238,7 +3238,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:110 new - + statistics.attachment_types_count Number of attachment types @@ -3249,7 +3249,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:114 new - + statistics.all_attachments_count Number of all attachments @@ -3260,7 +3260,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:118 new - + statistics.user_uploaded_attachments_count Number of user uploaded attachments @@ -3271,7 +3271,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:122 new - + statistics.private_attachments_count Number of private attachments @@ -3282,7 +3282,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Statistics\statistics.html.twig:126 new - + statistics.external_attachments_count Number of external attachments (URL) @@ -3294,7 +3294,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\backup_codes.html.twig:3 Part-DB1\templates\Users\backup_codes.html.twig:9 - + tfa_backup.codes.title Backup codes @@ -3304,7 +3304,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\backup_codes.html.twig:12 Part-DB1\templates\Users\backup_codes.html.twig:12 - + tfa_backup.codes.explanation Print out these codes and keep them in a safe place! @@ -3314,7 +3314,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\backup_codes.html.twig:13 Part-DB1\templates\Users\backup_codes.html.twig:13 - + tfa_backup.codes.help If you no longer have access to your device with the Authenticator App (lost smartphone, data loss, etc.) you can use one of these codes to access your account and possibly set up a new Authenticator App. Each of these codes can be used once, it is recommended to delete used codes. Anyone with access to these codes can potentially access your account, so keep them in a safe place. @@ -3324,7 +3324,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\backup_codes.html.twig:16 Part-DB1\templates\Users\backup_codes.html.twig:16 - + tfa_backup.username Username @@ -3334,7 +3334,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\backup_codes.html.twig:29 Part-DB1\templates\Users\backup_codes.html.twig:29 - + tfa_backup.codes.page_generated_on Page generated on %date% @@ -3344,7 +3344,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\backup_codes.html.twig:32 Part-DB1\templates\Users\backup_codes.html.twig:32 - + tfa_backup.codes.print Print @@ -3354,7 +3354,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\backup_codes.html.twig:35 Part-DB1\templates\Users\backup_codes.html.twig:35 - + tfa_backup.codes.copy_clipboard Copy to clipboard @@ -3371,7 +3371,7 @@ Sub elements will be moved upwards. templates\Users\user_info.html.twig:3 templates\Users\user_info.html.twig:6 - + user.info.label User information @@ -3385,7 +3385,7 @@ Sub elements will be moved upwards. templates\Users\user_info.html.twig:18 src\Form\UserSettingsType.php:32 - + user.firstName.label First name @@ -3399,7 +3399,7 @@ Sub elements will be moved upwards. templates\Users\user_info.html.twig:24 src\Form\UserSettingsType.php:35 - + user.lastName.label Last name @@ -3413,7 +3413,7 @@ Sub elements will be moved upwards. templates\Users\user_info.html.twig:30 src\Form\UserSettingsType.php:41 - + user.email.label Email @@ -3427,7 +3427,7 @@ Sub elements will be moved upwards. templates\Users\user_info.html.twig:37 src\Form\UserSettingsType.php:38 - + user.department.label Department @@ -3441,7 +3441,7 @@ Sub elements will be moved upwards. templates\Users\user_info.html.twig:47 src\Form\UserSettingsType.php:30 - + user.username.label User name @@ -3454,7 +3454,7 @@ Sub elements will be moved upwards. Part-DB1\src\Services\ElementTypeNameGenerator.php:93 templates\Users\user_info.html.twig:53 - + group.label Group: @@ -3464,7 +3464,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\user_info.html.twig:67 Part-DB1\templates\Users\user_info.html.twig:67 - + user.permissions Permissions @@ -3481,7 +3481,7 @@ Sub elements will be moved upwards. templates\Users\user_settings.html.twig:3 templates\Users\user_settings.html.twig:6 - + user.settings.label User settings @@ -3492,7 +3492,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\user_settings.html.twig:18 templates\Users\user_settings.html.twig:14 - + user_settings.data.label Personal data @@ -3503,7 +3503,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\user_settings.html.twig:22 templates\Users\user_settings.html.twig:18 - + user_settings.configuration.label Configuration @@ -3514,7 +3514,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\user_settings.html.twig:55 templates\Users\user_settings.html.twig:48 - + user.settings.change_pw Change password @@ -3524,7 +3524,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\_2fa_settings.html.twig:6 Part-DB1\templates\Users\_2fa_settings.html.twig:6 - + user.settings.2fa_settings Two-Factor Authentication @@ -3534,7 +3534,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\_2fa_settings.html.twig:13 Part-DB1\templates\Users\_2fa_settings.html.twig:13 - + tfa.settings.google.tab Authenticator app @@ -3544,7 +3544,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\_2fa_settings.html.twig:17 Part-DB1\templates\Users\_2fa_settings.html.twig:17 - + tfa.settings.bakup.tab Backup codes @@ -3554,7 +3554,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\_2fa_settings.html.twig:21 Part-DB1\templates\Users\_2fa_settings.html.twig:21 - + tfa.settings.u2f.tab Security keys (U2F) @@ -3564,7 +3564,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\_2fa_settings.html.twig:25 Part-DB1\templates\Users\_2fa_settings.html.twig:25 - + tfa.settings.trustedDevices.tab Trusted devices @@ -3574,7 +3574,7 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\_2fa_settings.html.twig:33 Part-DB1\templates\Users\_2fa_settings.html.twig:33 - + tfa_google.disable.confirm_title Do you really want to disable the Authenticator App? @@ -3584,10 +3584,10 @@ Sub elements will be moved upwards. Part-DB1\templates\Users\_2fa_settings.html.twig:33 Part-DB1\templates\Users\_2fa_settings.html.twig:33 - + tfa_google.disable.confirm_message - If you disable the Authenticator App, all backup codes will be deleted, so you may need to reprint them.<br> -Also note that without two-factor authentication your account is not as well protected against attackers! + +Also note that without two-factor authentication your account is not as well protected against attackers!]]> @@ -3595,7 +3595,7 @@ Also note that without two-factor authentication your account is not as well pro Part-DB1\templates\Users\_2fa_settings.html.twig:39 Part-DB1\templates\Users\_2fa_settings.html.twig:39 - + tfa_google.disabled_message Authenticator app deactivated! @@ -3605,9 +3605,9 @@ Also note that without two-factor authentication your account is not as well pro Part-DB1\templates\Users\_2fa_settings.html.twig:48 Part-DB1\templates\Users\_2fa_settings.html.twig:48 - + tfa_google.step.download - Download an authenticator app (e.g. <a class="link-external" target="_blank" href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2">Google Authenticator</a> oder <a class="link-external" target="_blank" href="https://play.google.com/store/apps/details?id=org.fedorahosted.freeotp">FreeOTP Authenticator</a>) + Google Authenticator oder FreeOTP Authenticator)]]> @@ -3615,7 +3615,7 @@ Also note that without two-factor authentication your account is not as well pro Part-DB1\templates\Users\_2fa_settings.html.twig:49 Part-DB1\templates\Users\_2fa_settings.html.twig:49 - + tfa_google.step.scan Scan the adjoining QR Code with the app or enter the data manually @@ -3625,7 +3625,7 @@ Also note that without two-factor authentication your account is not as well pro Part-DB1\templates\Users\_2fa_settings.html.twig:50 Part-DB1\templates\Users\_2fa_settings.html.twig:50 - + tfa_google.step.input_code Enter the generated code in the field below and confirm @@ -3635,7 +3635,7 @@ Also note that without two-factor authentication your account is not as well pro Part-DB1\templates\Users\_2fa_settings.html.twig:51 Part-DB1\templates\Users\_2fa_settings.html.twig:51 - + tfa_google.step.download_backup Print out your backup codes and store them in a safe place @@ -3645,7 +3645,7 @@ Also note that without two-factor authentication your account is not as well pro Part-DB1\templates\Users\_2fa_settings.html.twig:58 Part-DB1\templates\Users\_2fa_settings.html.twig:58 - + tfa_google.manual_setup Manual setup @@ -3655,7 +3655,7 @@ Also note that without two-factor authentication your account is not as well pro Part-DB1\templates\Users\_2fa_settings.html.twig:62 Part-DB1\templates\Users\_2fa_settings.html.twig:62 - + tfa_google.manual_setup.type Type @@ -3665,7 +3665,7 @@ Also note that without two-factor authentication your account is not as well pro Part-DB1\templates\Users\_2fa_settings.html.twig:63 Part-DB1\templates\Users\_2fa_settings.html.twig:63 - + tfa_google.manual_setup.username Username @@ -3675,7 +3675,7 @@ Also note that without two-factor authentication your account is not as well pro Part-DB1\templates\Users\_2fa_settings.html.twig:64 Part-DB1\templates\Users\_2fa_settings.html.twig:64 - + tfa_google.manual_setup.secret Secret @@ -3685,7 +3685,7 @@ Also note that without two-factor authentication your account is not as well pro Part-DB1\templates\Users\_2fa_settings.html.twig:65 Part-DB1\templates\Users\_2fa_settings.html.twig:65 - + tfa_google.manual_setup.digit_count Digit count @@ -3695,7 +3695,7 @@ Also note that without two-factor authentication your account is not as well pro Part-DB1\templates\Users\_2fa_settings.html.twig:74 Part-DB1\templates\Users\_2fa_settings.html.twig:74 - + tfa_google.enabled_message Authenticator App enabled @@ -3705,7 +3705,7 @@ Also note that without two-factor authentication your account is not as well pro Part-DB1\templates\Users\_2fa_settings.html.twig:83 Part-DB1\templates\Users\_2fa_settings.html.twig:83 - + tfa_backup.disabled Backup codes disabled. Setup authenticator app to enable backup codes. @@ -3717,7 +3717,7 @@ Also note that without two-factor authentication your account is not as well pro Part-DB1\templates\Users\_2fa_settings.html.twig:84 Part-DB1\templates\Users\_2fa_settings.html.twig:92 - + tfa_backup.explanation You can use these backup codes to access your account even if you lose the device with the Authenticator App. Print out the codes and keep them in a safe place. @@ -3727,7 +3727,7 @@ Also note that without two-factor authentication your account is not as well pro Part-DB1\templates\Users\_2fa_settings.html.twig:88 Part-DB1\templates\Users\_2fa_settings.html.twig:88 - + tfa_backup.reset_codes.confirm_title Really reset codes? @@ -3737,7 +3737,7 @@ Also note that without two-factor authentication your account is not as well pro Part-DB1\templates\Users\_2fa_settings.html.twig:88 Part-DB1\templates\Users\_2fa_settings.html.twig:88 - + tfa_backup.reset_codes.confirm_message This will delete all previous codes and generate a set of new codes. This cannot be undone. Remember to print out the new codes and store them in a safe place! @@ -3747,7 +3747,7 @@ Also note that without two-factor authentication your account is not as well pro Part-DB1\templates\Users\_2fa_settings.html.twig:91 Part-DB1\templates\Users\_2fa_settings.html.twig:91 - + tfa_backup.enabled Backup codes enabled @@ -3757,7 +3757,7 @@ Also note that without two-factor authentication your account is not as well pro Part-DB1\templates\Users\_2fa_settings.html.twig:99 Part-DB1\templates\Users\_2fa_settings.html.twig:99 - + tfa_backup.show_codes Show backup codes @@ -3767,7 +3767,7 @@ Also note that without two-factor authentication your account is not as well pro Part-DB1\templates\Users\_2fa_settings.html.twig:114 Part-DB1\templates\Users\_2fa_settings.html.twig:114 - + tfa_u2f.table_caption Registered security keys @@ -3777,7 +3777,7 @@ Also note that without two-factor authentication your account is not as well pro Part-DB1\templates\Users\_2fa_settings.html.twig:115 Part-DB1\templates\Users\_2fa_settings.html.twig:115 - + tfa_u2f.delete_u2f.confirm_title Really remove this security key? @@ -3787,7 +3787,7 @@ Also note that without two-factor authentication your account is not as well pro Part-DB1\templates\Users\_2fa_settings.html.twig:116 Part-DB1\templates\Users\_2fa_settings.html.twig:116 - + tfa_u2f.delete_u2f.confirm_message If you remove this key, then no more login with this key will be possible. If no security keys remain, two-factor authentication will be disabled. @@ -3797,7 +3797,7 @@ Also note that without two-factor authentication your account is not as well pro Part-DB1\templates\Users\_2fa_settings.html.twig:123 Part-DB1\templates\Users\_2fa_settings.html.twig:123 - + tfa_u2f.keys.name Key name @@ -3807,7 +3807,7 @@ Also note that without two-factor authentication your account is not as well pro Part-DB1\templates\Users\_2fa_settings.html.twig:124 Part-DB1\templates\Users\_2fa_settings.html.twig:124 - + tfa_u2f.keys.added_date Registration date @@ -3817,7 +3817,7 @@ Also note that without two-factor authentication your account is not as well pro Part-DB1\templates\Users\_2fa_settings.html.twig:134 Part-DB1\templates\Users\_2fa_settings.html.twig:134 - + tfa_u2f.key_delete Delete key @@ -3827,7 +3827,7 @@ Also note that without two-factor authentication your account is not as well pro Part-DB1\templates\Users\_2fa_settings.html.twig:141 Part-DB1\templates\Users\_2fa_settings.html.twig:141 - + tfa_u2f.no_keys_registered No keys registered yet. @@ -3837,7 +3837,7 @@ Also note that without two-factor authentication your account is not as well pro Part-DB1\templates\Users\_2fa_settings.html.twig:144 Part-DB1\templates\Users\_2fa_settings.html.twig:144 - + tfa_u2f.add_new_key Register new security key @@ -3847,10 +3847,10 @@ Also note that without two-factor authentication your account is not as well pro Part-DB1\templates\Users\_2fa_settings.html.twig:148 Part-DB1\templates\Users\_2fa_settings.html.twig:148 - + tfa_trustedDevices.explanation - When checking the second factor, the current computer can be marked as trustworthy, so no more two-factor checks on this computer are needed. -If you have done this incorrectly or if a computer is no longer trusted, you can reset the status of <i>all </i>computers here. + all computers here.]]> @@ -3858,7 +3858,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\Users\_2fa_settings.html.twig:149 Part-DB1\templates\Users\_2fa_settings.html.twig:149 - + tfa_trustedDevices.invalidate.confirm_title Really remove all trusted computers? @@ -3868,7 +3868,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\Users\_2fa_settings.html.twig:150 Part-DB1\templates\Users\_2fa_settings.html.twig:150 - + tfa_trustedDevices.invalidate.confirm_message You will have to perform two-factor authentication again on all computers. Make sure you have your two-factor device at hand. @@ -3878,7 +3878,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\Users\_2fa_settings.html.twig:154 Part-DB1\templates\Users\_2fa_settings.html.twig:154 - + tfa_trustedDevices.invalidate.btn Reset trusted devices @@ -3889,7 +3889,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\_navbar.html.twig:4 templates\base.html.twig:29 - + sidebar.toggle Toggle Sidebar @@ -3898,7 +3898,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\_navbar.html.twig:22 - + navbar.scanner.link Scanner @@ -3909,7 +3909,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\_navbar.html.twig:36 templates\base.html.twig:97 - + user.loggedin.label Logged in as @@ -3920,7 +3920,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\_navbar.html.twig:42 templates\base.html.twig:103 - + user.login Login @@ -3930,7 +3930,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\_navbar.html.twig:50 Part-DB1\templates\_navbar.html.twig:48 - + ui.toggle_darkmode Darkmode @@ -3944,7 +3944,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can templates\base.html.twig:106 src\Form\UserSettingsType.php:44 - + user.language_select Switch Language @@ -3955,7 +3955,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\_navbar_search.html.twig:4 templates\base.html.twig:49 - + search.options.label Search options @@ -3964,7 +3964,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\_navbar_search.html.twig:23 - + tags.label Tags @@ -3979,7 +3979,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can templates\Parts\show_part_info.html.twig:36 src\Form\PartType.php:77 - + storelocation.label Storage location @@ -3990,7 +3990,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\_navbar_search.html.twig:31 templates\base.html.twig:65 - + ordernumber.label.short supplier partnr. @@ -4003,7 +4003,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\ElementTypeNameGenerator.php:89 templates\base.html.twig:67 - + supplier.label Supplier @@ -4014,7 +4014,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\_navbar_search.html.twig:52 templates\base.html.twig:75 - + search.deactivateBarcode Deact. Barcode @@ -4025,7 +4025,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\_navbar_search.html.twig:56 templates\base.html.twig:77 - + search.regexmatching Reg.Ex. Matching @@ -4035,7 +4035,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\_navbar_search.html.twig:68 Part-DB1\templates\_navbar_search.html.twig:62 - + search.submit Go! @@ -4051,7 +4051,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can templates\base.html.twig:202 templates\base.html.twig:230 - + project.labelp Projects @@ -4064,7 +4064,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can templates\base.html.twig:192 templates\base.html.twig:220 - + actions Actions @@ -4077,7 +4077,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can templates\base.html.twig:196 templates\base.html.twig:224 - + datasource Data source @@ -4090,7 +4090,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can templates\base.html.twig:200 templates\base.html.twig:228 - + manufacturer.labelp Manufacturers @@ -4103,7 +4103,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can templates\base.html.twig:201 templates\base.html.twig:229 - + supplier.labelp Suppliers @@ -4119,7 +4119,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\PartController.php:173 Part-DB1\src\Controller\PartController.php:268 - + attachment.download_failed Download of the external attachment failed. @@ -4129,7 +4129,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\AdminPages\BaseAdminController.php:222 Part-DB1\src\Controller\AdminPages\BaseAdminController.php:190 - + entity.edit_flash Changes saved successful. @@ -4139,7 +4139,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\AdminPages\BaseAdminController.php:231 Part-DB1\src\Controller\AdminPages\BaseAdminController.php:196 - + entity.edit_flash.invalid Can not save changed. Please check your input! @@ -4149,7 +4149,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\AdminPages\BaseAdminController.php:302 Part-DB1\src\Controller\AdminPages\BaseAdminController.php:252 - + entity.created_flash Element created. @@ -4159,7 +4159,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\AdminPages\BaseAdminController.php:308 Part-DB1\src\Controller\AdminPages\BaseAdminController.php:258 - + entity.created_flash.invalid Could not create element. Please check your input! @@ -4170,7 +4170,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\AdminPages\BaseAdminController.php:352 src\Controller\BaseAdminController.php:154 - + attachment_type.deleted Element deleted! @@ -4186,7 +4186,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\UserSettingsController.php:150 Part-DB1\src\Controller\UserSettingsController.php:182 - + csfr_invalid CSFR Token invalid. Please reload this page or contact an administrator if this message stays. @@ -4195,7 +4195,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\LabelController.php:125 - + label_generator.no_entities_found No entities matching the range found. @@ -4206,7 +4206,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\LogController.php:154 new - + log.undo.target_not_found Target element could not be found in DB! @@ -4217,7 +4217,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\LogController.php:160 new - + log.undo.revert_success Reverted to timestamp successfully. @@ -4228,7 +4228,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\LogController.php:180 new - + log.undo.element_undelete_success Undeleted element successfully. @@ -4239,7 +4239,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\LogController.php:182 new - + log.undo.element_element_already_undeleted Element was already undeleted! @@ -4250,7 +4250,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\LogController.php:189 new - + log.undo.element_delete_success Element deleted successfully. @@ -4261,7 +4261,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\LogController.php:191 new - + log.undo.element.element_already_delted Element was already deleted! @@ -4272,7 +4272,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\LogController.php:198 new - + log.undo.element_change_undone Change undone successfully! @@ -4283,7 +4283,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\LogController.php:200 new - + log.undo.do_undelete_before You have to undelete the element before you can undo this change! @@ -4294,7 +4294,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\LogController.php:203 new - + log.undo.log_type_invalid This log entry can not be undone! @@ -4305,7 +4305,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\PartController.php:182 src\Controller\PartController.php:80 - + part.edited_flash Saved changes! @@ -4315,7 +4315,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\PartController.php:186 Part-DB1\src\Controller\PartController.php:186 - + part.edited_flash.invalid Error during saving: Please check your inputs! @@ -4325,7 +4325,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\PartController.php:216 Part-DB1\src\Controller\PartController.php:219 - + part.deleted Part deleted successful. @@ -4338,7 +4338,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can src\Controller\PartController.php:113 src\Controller\PartController.php:142 - + part.created_flash Part created! @@ -4348,7 +4348,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\PartController.php:308 Part-DB1\src\Controller\PartController.php:283 - + part.created_flash.invalid Error during creation: Please check your inputs! @@ -4358,7 +4358,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\ScanController.php:68 Part-DB1\src\Controller\ScanController.php:90 - + scan.qr_not_found No element found for the given barcode. @@ -4367,7 +4367,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\ScanController.php:71 - + scan.format_unknown Format unknown! @@ -4376,7 +4376,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\ScanController.php:86 - + scan.qr_success Element found. @@ -4386,7 +4386,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\SecurityController.php:114 Part-DB1\src\Controller\SecurityController.php:109 - + pw_reset.user_or_email Username / Email @@ -4396,7 +4396,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\SecurityController.php:131 Part-DB1\src\Controller\SecurityController.php:126 - + pw_reset.request.success Reset request was successful! Please check your emails for further instructions. @@ -4406,7 +4406,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\SecurityController.php:162 Part-DB1\src\Controller\SecurityController.php:160 - + pw_reset.username Username @@ -4416,7 +4416,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\SecurityController.php:165 Part-DB1\src\Controller\SecurityController.php:163 - + pw_reset.token Token @@ -4426,7 +4426,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\SecurityController.php:194 Part-DB1\src\Controller\SecurityController.php:192 - + pw_reset.new_pw.error Username or Token invalid! Please check your input. @@ -4436,7 +4436,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\SecurityController.php:196 Part-DB1\src\Controller\SecurityController.php:194 - + pw_reset.new_pw.success Password was reset successfully. You can now login with your new password. @@ -4446,7 +4446,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\UserController.php:107 Part-DB1\src\Controller\UserController.php:99 - + user.edit.reset_success All two-factor authentication methods were successfully disabled. @@ -4456,7 +4456,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\UserSettingsController.php:101 Part-DB1\src\Controller\UserSettingsController.php:92 - + tfa_backup.no_codes_enabled No backup codes enabled! @@ -4466,7 +4466,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\UserSettingsController.php:138 Part-DB1\src\Controller\UserSettingsController.php:132 - + tfa_u2f.u2f_delete.not_existing No security key with this ID is existing. @@ -4476,7 +4476,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\UserSettingsController.php:145 Part-DB1\src\Controller\UserSettingsController.php:139 - + tfa_u2f.u2f_delete.access_denied You can not delete the security keys of other users! @@ -4486,7 +4486,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\UserSettingsController.php:153 Part-DB1\src\Controller\UserSettingsController.php:147 - + tfa.u2f.u2f_delete.success Security key successfully removed. @@ -4496,7 +4496,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\UserSettingsController.php:188 Part-DB1\src\Controller\UserSettingsController.php:180 - + tfa_trustedDevice.invalidate.success Trusted devices successfully reset. @@ -4507,7 +4507,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\UserSettingsController.php:226 src\Controller\UserController.php:98 - + user.settings.saved_flash Settings saved! @@ -4518,7 +4518,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\UserSettingsController.php:288 src\Controller\UserController.php:130 - + user.settings.pw_changed_flash Password changed! @@ -4528,7 +4528,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\UserSettingsController.php:317 Part-DB1\src\Controller\UserSettingsController.php:306 - + user.settings.2fa.google.activated Authenticator App successfully activated. @@ -4538,7 +4538,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\UserSettingsController.php:328 Part-DB1\src\Controller\UserSettingsController.php:315 - + user.settings.2fa.google.disabled Authenticator App successfully deactivated. @@ -4548,7 +4548,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Controller\UserSettingsController.php:346 Part-DB1\src\Controller\UserSettingsController.php:332 - + user.settings.2fa.backup_codes.regenerated New backup codes successfully generated. @@ -4558,7 +4558,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\AttachmentDataTable.php:148 Part-DB1\src\DataTables\AttachmentDataTable.php:148 - + attachment.table.filename File name @@ -4568,7 +4568,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\AttachmentDataTable.php:153 Part-DB1\src\DataTables\AttachmentDataTable.php:153 - + attachment.table.filesize File size @@ -4588,7 +4588,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:193 Part-DB1\src\DataTables\PartsDataTable.php:200 - + true true @@ -4610,7 +4610,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:201 Part-DB1\src\Form\Type\SIUnitType.php:139 - + false false @@ -4620,7 +4620,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\Column\LogEntryTargetColumn.php:128 Part-DB1\src\DataTables\Column\LogEntryTargetColumn.php:119 - + log.target_deleted deleted @@ -4631,7 +4631,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\Column\RevertLogColumn.php:60 new - + log.undo.undelete Undelete element @@ -4642,7 +4642,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\Column\RevertLogColumn.php:66 new - + log.undo.undo Undo change @@ -4653,7 +4653,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\Column\RevertLogColumn.php:86 new - + log.undo.revert Revert element to this timestamp @@ -4663,7 +4663,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\LogDataTable.php:173 Part-DB1\src\DataTables\LogDataTable.php:161 - + log.id ID @@ -4673,7 +4673,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\LogDataTable.php:178 Part-DB1\src\DataTables\LogDataTable.php:166 - + log.timestamp Timestamp @@ -4683,7 +4683,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\LogDataTable.php:183 Part-DB1\src\DataTables\LogDataTable.php:171 - + log.type Event @@ -4693,7 +4693,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\LogDataTable.php:191 Part-DB1\src\DataTables\LogDataTable.php:179 - + log.level Level @@ -4703,7 +4703,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\LogDataTable.php:200 Part-DB1\src\DataTables\LogDataTable.php:188 - + log.user User @@ -4713,7 +4713,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\LogDataTable.php:213 Part-DB1\src\DataTables\LogDataTable.php:201 - + log.target_type Target type @@ -4723,7 +4723,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\LogDataTable.php:226 Part-DB1\src\DataTables\LogDataTable.php:214 - + log.target Target @@ -4734,7 +4734,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\LogDataTable.php:218 new - + log.extra Extra @@ -4744,7 +4744,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:168 Part-DB1\src\DataTables\PartsDataTable.php:116 - + part.table.name Name @@ -4754,7 +4754,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:178 Part-DB1\src\DataTables\PartsDataTable.php:126 - + part.table.id Id @@ -4764,7 +4764,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:182 Part-DB1\src\DataTables\PartsDataTable.php:130 - + part.table.description Description @@ -4774,7 +4774,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:185 Part-DB1\src\DataTables\PartsDataTable.php:133 - + part.table.category Category @@ -4784,7 +4784,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:190 Part-DB1\src\DataTables\PartsDataTable.php:138 - + part.table.footprint Footprint @@ -4794,7 +4794,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:194 Part-DB1\src\DataTables\PartsDataTable.php:142 - + part.table.manufacturer Manufacturer @@ -4804,7 +4804,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:197 Part-DB1\src\DataTables\PartsDataTable.php:145 - + part.table.storeLocations Storage locations @@ -4814,7 +4814,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:216 Part-DB1\src\DataTables\PartsDataTable.php:164 - + part.table.amount Amount @@ -4824,7 +4824,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:224 Part-DB1\src\DataTables\PartsDataTable.php:172 - + part.table.minamount Min. Amount @@ -4834,7 +4834,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:232 Part-DB1\src\DataTables\PartsDataTable.php:180 - + part.table.partUnit Measurement Unit @@ -4844,7 +4844,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:236 Part-DB1\src\DataTables\PartsDataTable.php:184 - + part.table.addedDate Created at @@ -4854,7 +4854,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:240 Part-DB1\src\DataTables\PartsDataTable.php:188 - + part.table.lastModified Last modified @@ -4864,7 +4864,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:244 Part-DB1\src\DataTables\PartsDataTable.php:192 - + part.table.needsReview Needs review @@ -4874,7 +4874,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:251 Part-DB1\src\DataTables\PartsDataTable.php:199 - + part.table.favorite Favorite @@ -4884,7 +4884,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:258 Part-DB1\src\DataTables\PartsDataTable.php:206 - + part.table.manufacturingStatus Status @@ -4898,7 +4898,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:210 Part-DB1\src\Form\Part\PartBaseType.php:88 - + m_status.unknown Unknown @@ -4910,7 +4910,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:211 Part-DB1\src\Form\Part\PartBaseType.php:88 - + m_status.announced Announced @@ -4922,7 +4922,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:212 Part-DB1\src\Form\Part\PartBaseType.php:88 - + m_status.active Active @@ -4934,7 +4934,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:213 Part-DB1\src\Form\Part\PartBaseType.php:88 - + m_status.nrfnd Not recommended for new designs @@ -4946,7 +4946,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:214 Part-DB1\src\Form\Part\PartBaseType.php:88 - + m_status.eol End of life @@ -4958,7 +4958,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:215 Part-DB1\src\Form\Part\PartBaseType.php:88 - + m_status.discontinued Discontinued @@ -4968,7 +4968,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:271 Part-DB1\src\DataTables\PartsDataTable.php:219 - + part.table.mpn MPN @@ -4978,7 +4978,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:275 Part-DB1\src\DataTables\PartsDataTable.php:223 - + part.table.mass Mass @@ -4988,7 +4988,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:279 Part-DB1\src\DataTables\PartsDataTable.php:227 - + part.table.tags Tags @@ -4998,7 +4998,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\DataTables\PartsDataTable.php:283 Part-DB1\src\DataTables\PartsDataTable.php:231 - + part.table.attachments Attachments @@ -5008,7 +5008,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\EventSubscriber\UserSystem\LoginSuccessSubscriber.php:82 Part-DB1\src\EventSubscriber\LoginSuccessListener.php:82 - + flash.login_successful Login successful @@ -5019,7 +5019,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AdminPages\ImportType.php:77 src\Form\ImportType.php:68 - + JSON JSON @@ -5030,7 +5030,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AdminPages\ImportType.php:77 src\Form\ImportType.php:68 - + XML XML @@ -5041,7 +5041,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AdminPages\ImportType.php:77 src\Form\ImportType.php:68 - + CSV CSV @@ -5052,7 +5052,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AdminPages\ImportType.php:77 src\Form\ImportType.php:68 - + YAML YAML @@ -5062,7 +5062,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AdminPages\ImportType.php:124 Part-DB1\src\Form\AdminPages\ImportType.php:124 - + import.abort_on_validation.help When this option is activated, the whole import process is aborted if invalid data is detected. If not selected, the invalid data is ignored and the importer will try to import the other elements. @@ -5073,7 +5073,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AdminPages\ImportType.php:86 src\Form\ImportType.php:70 - + import.csv_separator CSV separator @@ -5084,7 +5084,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AdminPages\ImportType.php:93 src\Form\ImportType.php:72 - + parent.label Parent element @@ -5095,7 +5095,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AdminPages\ImportType.php:101 src\Form\ImportType.php:75 - + import.file File @@ -5106,7 +5106,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AdminPages\ImportType.php:111 src\Form\ImportType.php:78 - + import.preserve_children Preserve child elements on import @@ -5117,7 +5117,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AdminPages\ImportType.php:120 src\Form\ImportType.php:80 - + import.abort_on_validation Abort on invalid data @@ -5128,7 +5128,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AdminPages\ImportType.php:132 src\Form\ImportType.php:85 - + import.btn Import @@ -5138,7 +5138,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AttachmentFormType.php:113 Part-DB1\src\Form\AttachmentFormType.php:109 - + attachment.edit.secure_file.help An attachment marked private can only be accessed by authenticated users with the proper permission. If this is activated no thumbnails are generated and access to file is less performant. @@ -5148,7 +5148,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AttachmentFormType.php:127 Part-DB1\src\Form\AttachmentFormType.php:123 - + attachment.edit.url.help You can specify an URL to an external file here, or input an keyword which is used to search in built-in resources (e.g. footprints) @@ -5158,7 +5158,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AttachmentFormType.php:82 Part-DB1\src\Form\AttachmentFormType.php:79 - + attachment.edit.name Name @@ -5168,7 +5168,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AttachmentFormType.php:85 Part-DB1\src\Form\AttachmentFormType.php:82 - + attachment.edit.attachment_type Attachment type @@ -5178,7 +5178,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AttachmentFormType.php:94 Part-DB1\src\Form\AttachmentFormType.php:91 - + attachment.edit.show_in_table Show in table @@ -5188,7 +5188,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AttachmentFormType.php:105 Part-DB1\src\Form\AttachmentFormType.php:102 - + attachment.edit.secure_file Private attachment @@ -5198,7 +5198,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AttachmentFormType.php:119 Part-DB1\src\Form\AttachmentFormType.php:115 - + attachment.edit.url URL @@ -5208,7 +5208,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AttachmentFormType.php:133 Part-DB1\src\Form\AttachmentFormType.php:129 - + attachment.edit.download_url Download external file @@ -5218,7 +5218,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\AttachmentFormType.php:146 Part-DB1\src\Form\AttachmentFormType.php:142 - + attachment.edit.file Upload file @@ -5228,7 +5228,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:68 Part-DB1\src\Services\ElementTypeNameGenerator.php:86 - + part.label Part @@ -5238,7 +5238,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:68 Part-DB1\src\Services\ElementTypeNameGenerator.php:87 - + part_lot.label Part lot @@ -5247,7 +5247,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:78 - + label_options.barcode_type.none None @@ -5256,7 +5256,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:78 - + label_options.barcode_type.qr QR Code (recommended) @@ -5265,7 +5265,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:78 - + label_options.barcode_type.code128 Code 128 (recommended) @@ -5274,7 +5274,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:78 - + label_options.barcode_type.code39 Code 39 (recommended) @@ -5283,7 +5283,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:78 - + label_options.barcode_type.code93 Code 93 @@ -5292,7 +5292,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:78 - + label_options.barcode_type.datamatrix Datamatrix @@ -5301,7 +5301,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:122 - + label_options.lines_mode.html Placeholders @@ -5310,7 +5310,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:122 - + label.options.lines_mode.twig Twig @@ -5319,16 +5319,16 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:126 - + label_options.lines_mode.help - If you select Twig here, the content field is interpreted as Twig template. See <a href="https://twig.symfony.com/doc/3.x/templates.html">Twig documentation</a> and <a href="https://docs.part-db.de/usage/labels.html#twig-mode">Wiki</a> for more information. + Twig documentation and Wiki for more information.]]> Part-DB1\src\Form\LabelOptionsType.php:47 - + label_options.page_size.label Label size @@ -5337,7 +5337,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:66 - + label_options.supported_elements.label Target type @@ -5346,7 +5346,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:75 - + label_options.barcode_type.label Barcode @@ -5355,7 +5355,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:102 - + label_profile.lines.label Content @@ -5364,7 +5364,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:111 - + label_options.additional_css.label Additional styles (CSS) @@ -5373,7 +5373,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:120 - + label_options.lines_mode.label Parser mode @@ -5382,7 +5382,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:51 - + label_options.width.placeholder Width @@ -5391,7 +5391,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelOptionsType.php:60 - + label_options.height.placeholder Height @@ -5400,7 +5400,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelSystem\LabelDialogType.php:49 - + label_generator.target_id.range_hint You can specify multiple IDs (e.g. 1,2,3) and/or a range (1-3) here to generate labels for multiple elements at once. @@ -5409,7 +5409,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelSystem\LabelDialogType.php:46 - + label_generator.target_id.label Target IDs @@ -5418,7 +5418,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelSystem\LabelDialogType.php:59 - + label_generator.update Update @@ -5427,7 +5427,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelSystem\ScanDialogType.php:36 - + scan_dialog.input Input @@ -5436,7 +5436,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\LabelSystem\ScanDialogType.php:44 - + scan_dialog.submit Submit @@ -5445,7 +5445,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\ParameterType.php:41 - + parameters.name.placeholder e.g. DC Current Gain @@ -5454,7 +5454,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\ParameterType.php:50 - + parameters.symbol.placeholder e.g. h_{FE} @@ -5463,7 +5463,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\ParameterType.php:60 - + parameters.text.placeholder e.g. Test conditions @@ -5472,7 +5472,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\ParameterType.php:71 - + parameters.max.placeholder e.g. 350 @@ -5481,7 +5481,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\ParameterType.php:82 - + parameters.min.placeholder e.g. 100 @@ -5490,7 +5490,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\ParameterType.php:93 - + parameters.typical.placeholder e.g. 200 @@ -5499,7 +5499,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\ParameterType.php:103 - + parameters.unit.placeholder e.g. V @@ -5508,7 +5508,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\ParameterType.php:114 - + parameter.group.placeholder e.g. Technical Specifications @@ -5518,7 +5518,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\OrderdetailType.php:72 Part-DB1\src\Form\Part\OrderdetailType.php:75 - + orderdetails.edit.supplierpartnr Supplier part number @@ -5528,7 +5528,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\OrderdetailType.php:81 Part-DB1\src\Form\Part\OrderdetailType.php:84 - + orderdetails.edit.supplier Supplier @@ -5538,7 +5538,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\OrderdetailType.php:87 Part-DB1\src\Form\Part\OrderdetailType.php:90 - + orderdetails.edit.url Link to offer @@ -5548,7 +5548,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\OrderdetailType.php:93 Part-DB1\src\Form\Part\OrderdetailType.php:96 - + orderdetails.edit.obsolete No longer available @@ -5558,7 +5558,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\OrderdetailType.php:75 Part-DB1\src\Form\Part\OrderdetailType.php:78 - + orderdetails.edit.supplierpartnr.placeholder e.g. BC 547 @@ -5568,7 +5568,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:101 Part-DB1\src\Form\Part\PartBaseType.php:99 - + part.edit.name Name @@ -5578,7 +5578,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:109 Part-DB1\src\Form\Part\PartBaseType.php:107 - + part.edit.description Description @@ -5588,7 +5588,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:120 Part-DB1\src\Form\Part\PartBaseType.php:118 - + part.edit.mininstock Minimum stock @@ -5598,7 +5598,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:129 Part-DB1\src\Form\Part\PartBaseType.php:127 - + part.edit.category Category @@ -5608,7 +5608,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:135 Part-DB1\src\Form\Part\PartBaseType.php:133 - + part.edit.footprint Footprint @@ -5618,7 +5618,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:142 Part-DB1\src\Form\Part\PartBaseType.php:140 - + part.edit.tags Tags @@ -5628,7 +5628,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:154 Part-DB1\src\Form\Part\PartBaseType.php:152 - + part.edit.manufacturer.label Manufacturer @@ -5638,7 +5638,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:161 Part-DB1\src\Form\Part\PartBaseType.php:159 - + part.edit.manufacturer_url.label Link to product page @@ -5648,7 +5648,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:167 Part-DB1\src\Form\Part\PartBaseType.php:165 - + part.edit.mpn Manufacturer part number @@ -5658,7 +5658,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:173 Part-DB1\src\Form\Part\PartBaseType.php:171 - + part.edit.manufacturing_status Manufacturing status @@ -5668,7 +5668,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:181 Part-DB1\src\Form\Part\PartBaseType.php:179 - + part.edit.needs_review Needs review @@ -5678,7 +5678,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:189 Part-DB1\src\Form\Part\PartBaseType.php:187 - + part.edit.is_favorite Favorite @@ -5688,7 +5688,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:197 Part-DB1\src\Form\Part\PartBaseType.php:195 - + part.edit.mass Mass @@ -5698,7 +5698,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:203 Part-DB1\src\Form\Part\PartBaseType.php:201 - + part.edit.partUnit Measuring unit @@ -5708,7 +5708,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:212 Part-DB1\src\Form\Part\PartBaseType.php:210 - + part.edit.comment Notes @@ -5718,7 +5718,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:250 Part-DB1\src\Form\Part\PartBaseType.php:246 - + part.edit.master_attachment Preview image @@ -5729,7 +5729,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:276 src\Form\PartType.php:91 - + part.edit.save Save changes @@ -5740,7 +5740,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:277 src\Form\PartType.php:92 - + part.edit.reset Reset changes @@ -5750,7 +5750,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:105 Part-DB1\src\Form\Part\PartBaseType.php:103 - + part.edit.name.placeholder e.g. BC547 @@ -5760,7 +5760,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:115 Part-DB1\src\Form\Part\PartBaseType.php:113 - + part.edit.description.placeholder e.g. NPN 45V, 0,1A, 0,5W @@ -5770,7 +5770,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartBaseType.php:123 Part-DB1\src\Form\Part\PartBaseType.php:121 - + part.editmininstock.placeholder e.g. 1 @@ -5780,7 +5780,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartLotType.php:69 Part-DB1\src\Form\Part\PartLotType.php:69 - + part_lot.edit.description Description @@ -5790,7 +5790,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartLotType.php:78 Part-DB1\src\Form\Part\PartLotType.php:78 - + part_lot.edit.location Storage location @@ -5800,7 +5800,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartLotType.php:89 Part-DB1\src\Form\Part\PartLotType.php:89 - + part_lot.edit.amount Amount @@ -5810,7 +5810,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartLotType.php:98 Part-DB1\src\Form\Part\PartLotType.php:97 - + part_lot.edit.instock_unknown Amount unknown @@ -5820,7 +5820,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartLotType.php:109 Part-DB1\src\Form\Part\PartLotType.php:108 - + part_lot.edit.needs_refill Needs refill @@ -5830,7 +5830,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartLotType.php:120 Part-DB1\src\Form\Part\PartLotType.php:119 - + part_lot.edit.expiration_date Expiration date @@ -5840,7 +5840,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Part\PartLotType.php:128 Part-DB1\src\Form\Part\PartLotType.php:125 - + part_lot.edit.comment Notes @@ -5850,7 +5850,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\Permissions\PermissionsType.php:99 Part-DB1\src\Form\Permissions\PermissionsType.php:99 - + perm.group.other Miscellaneous @@ -5860,7 +5860,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\TFAGoogleSettingsType.php:97 Part-DB1\src\Form\TFAGoogleSettingsType.php:97 - + tfa_google.enable Enable authenticator app @@ -5870,7 +5870,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\TFAGoogleSettingsType.php:101 Part-DB1\src\Form\TFAGoogleSettingsType.php:101 - + tfa_google.disable Deactivate authenticator app @@ -5880,7 +5880,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\TFAGoogleSettingsType.php:74 Part-DB1\src\Form\TFAGoogleSettingsType.php:74 - + google_confirmation Confirmation code @@ -5891,7 +5891,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\UserSettingsType.php:108 src\Form\UserSettingsType.php:46 - + user.timezone.label Timezone @@ -5901,7 +5901,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\UserSettingsType.php:133 Part-DB1\src\Form\UserSettingsType.php:132 - + user.currency.label Preferred currency @@ -5912,7 +5912,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\UserSettingsType.php:139 src\Form\UserSettingsType.php:53 - + save Apply changes @@ -5923,7 +5923,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\UserSettingsType.php:140 src\Form\UserSettingsType.php:54 - + reset Discard changes @@ -5934,7 +5934,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\UserSettingsType.php:104 src\Form\UserSettingsType.php:45 - + user_settings.language.placeholder Serverwide language @@ -5945,7 +5945,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Form\UserSettingsType.php:115 src\Form\UserSettingsType.php:48 - + user_settings.timezone.placeholder Serverwide Timezone @@ -5955,7 +5955,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\ElementTypeNameGenerator.php:79 Part-DB1\src\Services\ElementTypeNameGenerator.php:79 - + attachment.label Attachment @@ -5965,7 +5965,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\ElementTypeNameGenerator.php:81 Part-DB1\src\Services\ElementTypeNameGenerator.php:81 - + attachment_type.label Attachment type @@ -5975,7 +5975,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\ElementTypeNameGenerator.php:82 Part-DB1\src\Services\ElementTypeNameGenerator.php:82 - + project.label Project @@ -5985,7 +5985,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\ElementTypeNameGenerator.php:85 Part-DB1\src\Services\ElementTypeNameGenerator.php:85 - + measurement_unit.label Measurement unit @@ -5995,7 +5995,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\ElementTypeNameGenerator.php:90 Part-DB1\src\Services\ElementTypeNameGenerator.php:90 - + currency.label Currency @@ -6005,7 +6005,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\ElementTypeNameGenerator.php:91 Part-DB1\src\Services\ElementTypeNameGenerator.php:91 - + orderdetail.label Order detail @@ -6015,7 +6015,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\ElementTypeNameGenerator.php:92 Part-DB1\src\Services\ElementTypeNameGenerator.php:92 - + pricedetail.label Price detail @@ -6025,7 +6025,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\ElementTypeNameGenerator.php:94 Part-DB1\src\Services\ElementTypeNameGenerator.php:94 - + user.label User @@ -6034,7 +6034,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\ElementTypeNameGenerator.php:95 - + parameter.label Parameter @@ -6043,7 +6043,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\ElementTypeNameGenerator.php:96 - + label_profile.label Label profile @@ -6054,7 +6054,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\LogSystem\LogEntryExtraFormatter.php:161 new - + log.element_deleted.old_name.unknown Unknown @@ -6064,7 +6064,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\MarkdownParser.php:73 Part-DB1\src\Services\MarkdownParser.php:73 - + markdown.loading Loading markdown. If this message does not disappear, try to reload the page. @@ -6074,7 +6074,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\PasswordResetManager.php:98 Part-DB1\src\Services\PasswordResetManager.php:98 - + pw_reset.email.subject Password reset for your Part-DB account @@ -6083,7 +6083,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:108 - + tree.tools.tools Tools @@ -6094,7 +6094,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:107 src\Services\ToolsTreeBuilder.php:74 - + tree.tools.edit Edit @@ -6105,7 +6105,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:108 src\Services\ToolsTreeBuilder.php:81 - + tree.tools.show Show @@ -6115,7 +6115,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:111 Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:109 - + tree.tools.system System @@ -6124,7 +6124,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:123 - + tree.tools.tools.label_dialog Label generator @@ -6133,7 +6133,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:130 - + tree.tools.tools.label_scanner Scanner @@ -6144,7 +6144,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:126 src\Services\ToolsTreeBuilder.php:62 - + tree.tools.edit.attachment_types Attachment types @@ -6155,7 +6155,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:132 src\Services\ToolsTreeBuilder.php:64 - + tree.tools.edit.categories Categories @@ -6166,7 +6166,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:138 src\Services\ToolsTreeBuilder.php:66 - + tree.tools.edit.projects Projects @@ -6177,7 +6177,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:144 src\Services\ToolsTreeBuilder.php:68 - + tree.tools.edit.suppliers Suppliers @@ -6188,7 +6188,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:150 src\Services\ToolsTreeBuilder.php:70 - + tree.tools.edit.manufacturer Manufacturers @@ -6198,7 +6198,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:179 Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:156 - + tree.tools.edit.storelocation Storage locations @@ -6208,7 +6208,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:185 Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:162 - + tree.tools.edit.footprint Footprints @@ -6218,7 +6218,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:191 Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:168 - + tree.tools.edit.currency Currencies @@ -6228,7 +6228,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:197 Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:174 - + tree.tools.edit.measurement_unit Measurement Unit @@ -6237,7 +6237,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:203 - + tree.tools.edit.label_profile Label profiles @@ -6247,7 +6247,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:209 Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:180 - + tree.tools.edit.part New part @@ -6258,7 +6258,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:197 src\Services\ToolsTreeBuilder.php:77 - + tree.tools.show.all_parts Show all parts @@ -6268,7 +6268,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:232 Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:203 - + tree.tools.show.all_attachments Attachments @@ -6279,7 +6279,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:210 new - + tree.tools.show.statistics Statistics @@ -6289,7 +6289,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:258 Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:229 - + tree.tools.system.users Users @@ -6299,7 +6299,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:264 Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:235 - + tree.tools.system.groups Groups @@ -6310,7 +6310,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:242 new - + tree.tools.system.event_log Event log @@ -6321,7 +6321,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\src\Services\Trees\TreeViewGenerator.php:95 src\Services\TreeBuilder.php:124 - + entity.tree.new New Element @@ -6331,7 +6331,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\Parts\info\_attachments_info.html.twig:34 obsolete - + attachment.external_file External file @@ -6341,7 +6341,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can Part-DB1\templates\Parts\info\_attachments_info.html.twig:62 obsolete - + attachment.edit Edit @@ -6352,7 +6352,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can templates\base.html.twig:88 obsolete - + barcode.scan Scan Barcode @@ -6363,7 +6363,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can src\Form\UserSettingsType.php:49 obsolete - + user.theme.label Theme @@ -6374,7 +6374,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can src\Form\UserSettingsType.php:50 obsolete - + user_settings.theme.placeholder Serverwide Theme @@ -6385,7 +6385,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can new obsolete - + log.user_login.ip IP @@ -6399,7 +6399,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can new obsolete - + log.undo_mode.undo Change undone @@ -6413,7 +6413,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can new obsolete - + log.undo_mode.revert Element reverted @@ -6424,7 +6424,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can new obsolete - + log.element_created.original_instock Old instock @@ -6435,7 +6435,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can new obsolete - + log.element_deleted.old_name Old name @@ -6446,7 +6446,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can new obsolete - + log.element_edited.changed_fields Changed fields @@ -6457,7 +6457,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can new obsolete - + log.instock_changed.comment Comment @@ -6468,7 +6468,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can new obsolete - + log.collection_deleted.deleted Deleted element: @@ -6479,7 +6479,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + go.exclamation Go! @@ -6490,7 +6490,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + language.english English @@ -6501,7 +6501,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + language.german German @@ -6511,7 +6511,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + flash.password_change_needed Password change needed! @@ -6521,7 +6521,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + attachment.table.type Attachment type @@ -6531,7 +6531,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + attachment.table.element Associated element @@ -6541,7 +6541,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + attachment.edit.isPicture Picture? @@ -6551,7 +6551,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + attachment.edit.is3DModel 3D model? @@ -6561,7 +6561,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + attachment.edit.isBuiltin Builtin? @@ -6571,7 +6571,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.default_comment.placeholder e.g. useful for switching @@ -6581,7 +6581,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + tfa_backup.regenerate_codes Generate new backup codes @@ -6591,7 +6591,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + validator.noneofitschild.self A element can not be its own parent. @@ -6601,7 +6601,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + validator.noneofitschild.children The parent can not be one of the children of itself. @@ -6611,7 +6611,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + validator.part_lot.location_full.no_increasment The storage location was marked as full, so you can not increase the instock amount. (New amount max. {{ old_amount }}) @@ -6621,7 +6621,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + validator.part_lot.location_full The storage location was marked as full, so you can not add a new part to it. @@ -6631,7 +6631,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + validator.part_lot.only_existing The storage location was marked as "only existing", so you can not add new part to it. @@ -6641,7 +6641,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + validator.part_lot.single_part The storage location was marked as "single part", so you can not add a new part to it. @@ -6651,7 +6651,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + m_status.active.help The part is currently and in the foreseeable future in production @@ -6661,7 +6661,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + m_status.announced.help The part was announced but is not available yet. @@ -6671,7 +6671,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + m_status.discontinued.help The part is discontinued and not produced anymore. @@ -6681,7 +6681,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + m_status.eol.help The product has reached its end-of-life and the production will be stopped soon. @@ -6691,7 +6691,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + m_status.nrfnd.help The part is currently in production but is not recommended for new designs. @@ -6701,7 +6701,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + m_status.unknown.help The manufacturing status of the part is not known. @@ -6711,7 +6711,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + flash.success Success @@ -6721,7 +6721,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + flash.error Error @@ -6731,7 +6731,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + flash.warning Warning @@ -6741,7 +6741,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + flash.notice Notice @@ -6751,7 +6751,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + flash.info Info @@ -6761,7 +6761,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + validator.noLockout You can not withdraw yourself the "change permission" permission, to prevent that you lockout yourself accidentally. @@ -6771,7 +6771,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + attachment_type.edit.filetype_filter Allowed file extensions @@ -6781,7 +6781,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + attachment_type.edit.filetype_filter.help You can specify a comma separated list of file extension or mime types, which an uploaded file must have when assigned to this attachment type. To allow all supported image files, you can use image/*. @@ -6791,7 +6791,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + attachment_type.edit.filetype_filter.placeholder e.g. .txt, application/pdf, image/* @@ -6802,7 +6802,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + part.name.placeholder e.g. BC547 @@ -6812,7 +6812,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + entity.edit.not_selectable Not selectable @@ -6822,7 +6822,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + entity.edit.not_selectable.help If this option is activated, this element can not be assigned to a part property. Useful if this element is just used for grouping. @@ -6832,7 +6832,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + bbcode.hint You can use BBCode here (e.g. [b]Bold[/b]) @@ -6842,7 +6842,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + entity.create Create element @@ -6852,7 +6852,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + entity.edit.save Save @@ -6862,7 +6862,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.disable_footprints Disable footprints @@ -6872,7 +6872,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.disable_footprints.help If this option is activated, the footprint property is disabled for all parts with this category. @@ -6882,7 +6882,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.disable_manufacturers Disable manufacturers @@ -6892,7 +6892,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.disable_manufacturers.help If this option is activated, the manufacturer property is disabled for all parts with this category. @@ -6902,7 +6902,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.disable_autodatasheets Disable automatic datasheet links @@ -6912,7 +6912,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.disable_autodatasheets.help If this option is activated, no automatic links to datasheets are created for parts with this category. @@ -6922,7 +6922,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.disable_properties Disable properties @@ -6932,7 +6932,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.disable_properties.help If this option is activated, the part properties are disabled for parts with this category. @@ -6942,7 +6942,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.partname_hint Part name hint @@ -6952,7 +6952,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.partname_hint.placeholder e.g. 100nF @@ -6962,7 +6962,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.partname_regex Name filter @@ -6972,7 +6972,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.default_description Default description @@ -6982,7 +6982,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.default_description.placeholder e.g. Capacitor, 10mm x 10mm, SMD @@ -6992,7 +6992,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + category.edit.default_comment Default notes @@ -7002,7 +7002,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + company.edit.address Address @@ -7012,7 +7012,7 @@ If you have done this incorrectly or if a computer is no longer trusted, you can obsolete obsolete - + company.edit.address.placeholder e.g. Examplestreet 314 Exampletown @@ -7023,7 +7023,7 @@ Exampletown obsolete obsolete - + company.edit.phone_number Phone number @@ -7033,7 +7033,7 @@ Exampletown obsolete obsolete - + company.edit.phone_number.placeholder +49 12345 6789 @@ -7043,7 +7043,7 @@ Exampletown obsolete obsolete - + company.edit.fax_number Fax number @@ -7053,7 +7053,7 @@ Exampletown obsolete obsolete - + company.edit.email Email @@ -7063,7 +7063,7 @@ Exampletown obsolete obsolete - + company.edit.email.placeholder e.g. contact@foo.bar @@ -7073,7 +7073,7 @@ Exampletown obsolete obsolete - + company.edit.website Website @@ -7083,7 +7083,7 @@ Exampletown obsolete obsolete - + company.edit.website.placeholder https://www.foo.bar @@ -7093,7 +7093,7 @@ Exampletown obsolete obsolete - + company.edit.auto_product_url Product URL @@ -7103,7 +7103,7 @@ Exampletown obsolete obsolete - + company.edit.auto_product_url.help This field is used to determine a link to the part on the company page. %PARTNUMBER% will be replaced with the order number. @@ -7113,7 +7113,7 @@ Exampletown obsolete obsolete - + company.edit.auto_product_url.placeholder https://foo.bar/product/%PARTNUMBER% @@ -7123,7 +7123,7 @@ Exampletown obsolete obsolete - + currency.edit.iso_code ISO code @@ -7133,7 +7133,7 @@ Exampletown obsolete obsolete - + currency.edit.exchange_rate Exchange rate @@ -7143,7 +7143,7 @@ Exampletown obsolete obsolete - + footprint.edit.3d_model 3D model @@ -7153,7 +7153,7 @@ Exampletown obsolete obsolete - + mass_creation.lines Input @@ -7163,7 +7163,7 @@ Exampletown obsolete obsolete - + mass_creation.lines.placeholder Element 1 Element 1.1 @@ -7178,7 +7178,7 @@ Element 3 obsolete obsolete - + entity.mass_creation.btn Create @@ -7188,7 +7188,7 @@ Element 3 obsolete obsolete - + measurement_unit.edit.is_integer Is integer @@ -7198,7 +7198,7 @@ Element 3 obsolete obsolete - + measurement_unit.edit.is_integer.help If this option is activated, all values with this unit will be rounded to whole numbers. @@ -7208,7 +7208,7 @@ Element 3 obsolete obsolete - + measurement_unit.edit.use_si_prefix Use SI prefix @@ -7218,7 +7218,7 @@ Element 3 obsolete obsolete - + measurement_unit.edit.use_si_prefix.help If this option is activated, values are outputted with SI prefixes (e.g. 1,2kg instead of 1200g) @@ -7228,7 +7228,7 @@ Element 3 obsolete obsolete - + measurement_unit.edit.unit_symbol Unit symbol @@ -7238,7 +7238,7 @@ Element 3 obsolete obsolete - + measurement_unit.edit.unit_symbol.placeholder e.g. m @@ -7248,7 +7248,7 @@ Element 3 obsolete obsolete - + storelocation.edit.is_full.label Storelocation full @@ -7258,7 +7258,7 @@ Element 3 obsolete obsolete - + storelocation.edit.is_full.help If this option is selected, it is neither possible to add new parts to this storelocation or to increase the amount of existing parts. @@ -7268,7 +7268,7 @@ Element 3 obsolete obsolete - + storelocation.limit_to_existing.label Limit to existing parts @@ -7278,7 +7278,7 @@ Element 3 obsolete obsolete - + storelocation.limit_to_existing.help If this option is activated, it is not possible to add new parts to this storelocation, but the amount of existing parts can be increased. @@ -7288,7 +7288,7 @@ Element 3 obsolete obsolete - + storelocation.only_single_part.label Only single part @@ -7298,7 +7298,7 @@ Element 3 obsolete obsolete - + storelocation.only_single_part.help If this option is activated, only a single part (with every amount) can be assigned to this storage location. Useful for small SMD boxes or feeders. @@ -7308,7 +7308,7 @@ Element 3 obsolete obsolete - + storelocation.storage_type.label Storage type @@ -7318,7 +7318,7 @@ Element 3 obsolete obsolete - + storelocation.storage_type.help You can select a measurement unit here, which a part must have to be able to be assigned to this storage location @@ -7328,7 +7328,7 @@ Element 3 obsolete obsolete - + supplier.edit.default_currency Default currency @@ -7338,7 +7338,7 @@ Element 3 obsolete obsolete - + supplier.shipping_costs.label Shipping Costs @@ -7348,7 +7348,7 @@ Element 3 obsolete obsolete - + user.username.placeholder e.g. j.doe @@ -7358,7 +7358,7 @@ Element 3 obsolete obsolete - + user.firstName.placeholder e.g John @@ -7368,7 +7368,7 @@ Element 3 obsolete obsolete - + user.lastName.placeholder e.g. Doe @@ -7378,7 +7378,7 @@ Element 3 obsolete obsolete - + user.email.placeholder j.doe@ecorp.com @@ -7388,7 +7388,7 @@ Element 3 obsolete obsolete - + user.department.placeholder e.g. Development @@ -7398,7 +7398,7 @@ Element 3 obsolete obsolete - + user.settings.pw_new.label New password @@ -7408,7 +7408,7 @@ Element 3 obsolete obsolete - + user.settings.pw_confirm.label Confirm new password @@ -7418,7 +7418,7 @@ Element 3 obsolete obsolete - + user.edit.needs_pw_change User needs to change password @@ -7428,7 +7428,7 @@ Element 3 obsolete obsolete - + user.edit.user_disabled User disabled (no login possible) @@ -7438,7 +7438,7 @@ Element 3 obsolete obsolete - + user.create Create user @@ -7448,7 +7448,7 @@ Element 3 obsolete obsolete - + user.edit.save Save @@ -7458,7 +7458,7 @@ Element 3 obsolete obsolete - + entity.edit.reset Discard changes @@ -7469,7 +7469,7 @@ Element 3 obsolete obsolete - + part.withdraw.btn Withdraw @@ -7480,7 +7480,7 @@ Element 3 obsolete obsolete - + part.withdraw.comment: Comment/Purpose @@ -7491,7 +7491,7 @@ Element 3 obsolete obsolete - + part.add.caption Add parts @@ -7502,7 +7502,7 @@ Element 3 obsolete obsolete - + part.add.btn Add @@ -7513,7 +7513,7 @@ Element 3 obsolete obsolete - + part.add.comment Comment/Purpose @@ -7524,7 +7524,7 @@ Element 3 obsolete obsolete - + admin.comment Notes @@ -7535,7 +7535,7 @@ Element 3 obsolete obsolete - + manufacturer_url.label Manufacturer link @@ -7546,7 +7546,7 @@ Element 3 obsolete obsolete - + part.description.placeholder e.g. NPN 45V 0,1A 0,5W @@ -7557,7 +7557,7 @@ Element 3 obsolete obsolete - + part.instock.placeholder e.g. 10 @@ -7568,7 +7568,7 @@ Element 3 obsolete obsolete - + part.mininstock.placeholder e.g. 5 @@ -7578,7 +7578,7 @@ Element 3 obsolete obsolete - + part.order.price_per Price per @@ -7588,7 +7588,7 @@ Element 3 obsolete obsolete - + part.withdraw.caption Withdraw parts @@ -7598,7 +7598,7 @@ Element 3 obsolete obsolete - + datatable.datatable.lengthMenu _MENU_ @@ -7608,7 +7608,7 @@ Element 3 obsolete obsolete - + perm.group.parts Parts @@ -7618,7 +7618,7 @@ Element 3 obsolete obsolete - + perm.group.structures Data structures @@ -7628,7 +7628,7 @@ Element 3 obsolete obsolete - + perm.group.system System @@ -7638,7 +7638,7 @@ Element 3 obsolete obsolete - + perm.parts Parts @@ -7648,7 +7648,7 @@ Element 3 obsolete obsolete - + perm.read View @@ -7658,7 +7658,7 @@ Element 3 obsolete obsolete - + perm.edit Edit @@ -7668,7 +7668,7 @@ Element 3 obsolete obsolete - + perm.create Create @@ -7678,7 +7678,7 @@ Element 3 obsolete obsolete - + perm.part.move Change category @@ -7688,7 +7688,7 @@ Element 3 obsolete obsolete - + perm.delete Delete @@ -7698,7 +7698,7 @@ Element 3 obsolete obsolete - + perm.part.search Search @@ -7708,7 +7708,7 @@ Element 3 obsolete obsolete - + perm.part.all_parts List all parts @@ -7718,7 +7718,7 @@ Element 3 obsolete obsolete - + perm.part.no_price_parts List parts without price info @@ -7728,7 +7728,7 @@ Element 3 obsolete obsolete - + perm.part.obsolete_parts List obsolete parts @@ -7738,7 +7738,7 @@ Element 3 obsolete obsolete - + perm.part.unknown_instock_parts Show parts with unknown instock @@ -7748,7 +7748,7 @@ Element 3 obsolete obsolete - + perm.part.change_favorite Change favorite status @@ -7758,7 +7758,7 @@ Element 3 obsolete obsolete - + perm.part.show_favorite List favorite parts @@ -7768,7 +7768,7 @@ Element 3 obsolete obsolete - + perm.part.show_last_edit_parts Show last edited/added parts @@ -7778,7 +7778,7 @@ Element 3 obsolete obsolete - + perm.part.show_users Show last modifying user @@ -7788,7 +7788,7 @@ Element 3 obsolete obsolete - + perm.part.show_history Show history @@ -7798,7 +7798,7 @@ Element 3 obsolete obsolete - + perm.part.name Name @@ -7808,7 +7808,7 @@ Element 3 obsolete obsolete - + perm.part.description Description @@ -7818,7 +7818,7 @@ Element 3 obsolete obsolete - + perm.part.instock Instock @@ -7828,7 +7828,7 @@ Element 3 obsolete obsolete - + perm.part.mininstock Minimum instock @@ -7838,7 +7838,7 @@ Element 3 obsolete obsolete - + perm.part.comment Notes @@ -7848,7 +7848,7 @@ Element 3 obsolete obsolete - + perm.part.storelocation Storelocation @@ -7858,7 +7858,7 @@ Element 3 obsolete obsolete - + perm.part.manufacturer Manufacturer @@ -7868,7 +7868,7 @@ Element 3 obsolete obsolete - + perm.part.orderdetails Order information @@ -7878,7 +7878,7 @@ Element 3 obsolete obsolete - + perm.part.prices Prices @@ -7888,7 +7888,7 @@ Element 3 obsolete obsolete - + perm.part.attachments File attachments @@ -7898,7 +7898,7 @@ Element 3 obsolete obsolete - + perm.part.order Orders @@ -7908,7 +7908,7 @@ Element 3 obsolete obsolete - + perm.storelocations Storelocations @@ -7918,7 +7918,7 @@ Element 3 obsolete obsolete - + perm.move Move @@ -7928,7 +7928,7 @@ Element 3 obsolete obsolete - + perm.list_parts List parts @@ -7938,7 +7938,7 @@ Element 3 obsolete obsolete - + perm.part.footprints Footprints @@ -7948,7 +7948,7 @@ Element 3 obsolete obsolete - + perm.part.categories Categories @@ -7958,7 +7958,7 @@ Element 3 obsolete obsolete - + perm.part.supplier Suppliers @@ -7968,7 +7968,7 @@ Element 3 obsolete obsolete - + perm.part.manufacturers Manufacturers @@ -7978,7 +7978,7 @@ Element 3 obsolete obsolete - + perm.projects Projects @@ -7988,7 +7988,7 @@ Element 3 obsolete obsolete - + perm.part.attachment_types Attachment types @@ -7998,7 +7998,7 @@ Element 3 obsolete obsolete - + perm.tools.import Import @@ -8008,7 +8008,7 @@ Element 3 obsolete obsolete - + perm.tools.labels Labels @@ -8018,7 +8018,7 @@ Element 3 obsolete obsolete - + perm.tools.calculator Resistor calculator @@ -8028,7 +8028,7 @@ Element 3 obsolete obsolete - + perm.tools.footprints Footprints @@ -8038,7 +8038,7 @@ Element 3 obsolete obsolete - + perm.tools.ic_logos IC logos @@ -8048,7 +8048,7 @@ Element 3 obsolete obsolete - + perm.tools.statistics Statistics @@ -8058,7 +8058,7 @@ Element 3 obsolete obsolete - + perm.edit_permissions Edit permissions @@ -8068,7 +8068,7 @@ Element 3 obsolete obsolete - + perm.users.edit_user_name Edit user name @@ -8078,7 +8078,7 @@ Element 3 obsolete obsolete - + perm.users.edit_change_group Change group @@ -8088,7 +8088,7 @@ Element 3 obsolete obsolete - + perm.users.edit_infos Edit info @@ -8098,7 +8098,7 @@ Element 3 obsolete obsolete - + perm.users.edit_permissions Edit permissions @@ -8108,7 +8108,7 @@ Element 3 obsolete obsolete - + perm.users.set_password Set password @@ -8118,7 +8118,7 @@ Element 3 obsolete obsolete - + perm.users.change_user_settings Change user settings @@ -8128,7 +8128,7 @@ Element 3 obsolete obsolete - + perm.database.see_status Show status @@ -8138,7 +8138,7 @@ Element 3 obsolete obsolete - + perm.database.update_db Update DB @@ -8148,7 +8148,7 @@ Element 3 obsolete obsolete - + perm.database.read_db_settings Read DB settings @@ -8158,7 +8158,7 @@ Element 3 obsolete obsolete - + perm.database.write_db_settings Write DB settings @@ -8168,7 +8168,7 @@ Element 3 obsolete obsolete - + perm.config.read_config Read config @@ -8178,7 +8178,7 @@ Element 3 obsolete obsolete - + perm.config.edit_config Edit config @@ -8188,7 +8188,7 @@ Element 3 obsolete obsolete - + perm.config.server_info Server info @@ -8198,7 +8198,7 @@ Element 3 obsolete obsolete - + perm.config.use_debug Use debug tools @@ -8208,7 +8208,7 @@ Element 3 obsolete obsolete - + perm.show_logs Show logs @@ -8218,7 +8218,7 @@ Element 3 obsolete obsolete - + perm.delete_logs Delete logs @@ -8228,7 +8228,7 @@ Element 3 obsolete obsolete - + perm.self.edit_infos Edit info @@ -8238,7 +8238,7 @@ Element 3 obsolete obsolete - + perm.self.edit_username Edit username @@ -8248,7 +8248,7 @@ Element 3 obsolete obsolete - + perm.self.show_permissions View permissions @@ -8258,7 +8258,7 @@ Element 3 obsolete obsolete - + perm.self.show_logs Show own log entries @@ -8268,7 +8268,7 @@ Element 3 obsolete obsolete - + perm.self.create_labels Create labels @@ -8278,7 +8278,7 @@ Element 3 obsolete obsolete - + perm.self.edit_options Edit options @@ -8288,7 +8288,7 @@ Element 3 obsolete obsolete - + perm.self.delete_profiles Delete profiles @@ -8298,7 +8298,7 @@ Element 3 obsolete obsolete - + perm.self.edit_profiles Edit profiles @@ -8308,7 +8308,7 @@ Element 3 obsolete obsolete - + perm.part.tools Tools @@ -8318,7 +8318,7 @@ Element 3 obsolete obsolete - + perm.groups Groups @@ -8328,7 +8328,7 @@ Element 3 obsolete obsolete - + perm.users Users @@ -8338,7 +8338,7 @@ Element 3 obsolete obsolete - + perm.database Database @@ -8348,7 +8348,7 @@ Element 3 obsolete obsolete - + perm.config Configuration @@ -8358,7 +8358,7 @@ Element 3 obsolete obsolete - + perm.system System @@ -8368,7 +8368,7 @@ Element 3 obsolete obsolete - + perm.self Own user @@ -8378,7 +8378,7 @@ Element 3 obsolete obsolete - + perm.labels Labels @@ -8388,7 +8388,7 @@ Element 3 obsolete obsolete - + perm.part.category Category @@ -8398,7 +8398,7 @@ Element 3 obsolete obsolete - + perm.part.minamount Minimum amount @@ -8408,7 +8408,7 @@ Element 3 obsolete obsolete - + perm.part.footprint Footprint @@ -8418,7 +8418,7 @@ Element 3 obsolete obsolete - + perm.part.mpn MPN @@ -8428,7 +8428,7 @@ Element 3 obsolete obsolete - + perm.part.status Manufacturing status @@ -8438,7 +8438,7 @@ Element 3 obsolete obsolete - + perm.part.tags Tags @@ -8448,7 +8448,7 @@ Element 3 obsolete obsolete - + perm.part.unit Part unit @@ -8458,7 +8458,7 @@ Element 3 obsolete obsolete - + perm.part.mass Mass @@ -8468,7 +8468,7 @@ Element 3 obsolete obsolete - + perm.part.lots Part lots @@ -8478,7 +8478,7 @@ Element 3 obsolete obsolete - + perm.show_users Show last modifying user @@ -8488,7 +8488,7 @@ Element 3 obsolete obsolete - + perm.currencies Currencies @@ -8498,7 +8498,7 @@ Element 3 obsolete obsolete - + perm.measurement_units Measurement unit @@ -8508,7 +8508,7 @@ Element 3 obsolete obsolete - + user.settings.pw_old.label Old password @@ -8518,7 +8518,7 @@ Element 3 obsolete obsolete - + pw_reset.submit Reset password @@ -8528,7 +8528,7 @@ Element 3 obsolete obsolete - + u2f_two_factor Security key (U2F) @@ -8538,13 +8538,13 @@ Element 3 obsolete obsolete - + google Google - + tfa.provider.webauthn_two_factor_provider Security key @@ -8554,7 +8554,7 @@ Element 3 obsolete obsolete - + tfa.provider.google Authenticator app @@ -8564,7 +8564,7 @@ Element 3 obsolete obsolete - + Login successful Login successful @@ -8574,7 +8574,7 @@ Element 3 obsolete obsolete - + log.type.exception Unhandled exception (obsolete) @@ -8584,7 +8584,7 @@ Element 3 obsolete obsolete - + log.type.user_login User login @@ -8594,7 +8594,7 @@ Element 3 obsolete obsolete - + log.type.user_logout User logout @@ -8604,7 +8604,7 @@ Element 3 obsolete obsolete - + log.type.unknown Unknown @@ -8614,7 +8614,7 @@ Element 3 obsolete obsolete - + log.type.element_created Element created @@ -8624,7 +8624,7 @@ Element 3 obsolete obsolete - + log.type.element_edited Element edited @@ -8634,7 +8634,7 @@ Element 3 obsolete obsolete - + log.type.element_deleted Element deleted @@ -8644,7 +8644,7 @@ Element 3 obsolete obsolete - + log.type.database_updated Database updated @@ -8653,7 +8653,7 @@ Element 3 obsolete - + perm.revert_elements Revert element @@ -8662,7 +8662,7 @@ Element 3 obsolete - + perm.show_history Show history @@ -8671,7 +8671,7 @@ Element 3 obsolete - + perm.tools.lastActivity Show last activity @@ -8680,7 +8680,7 @@ Element 3 obsolete - + perm.tools.timeTravel Show old element versions (time travel) @@ -8689,7 +8689,7 @@ Element 3 obsolete - + tfa_u2f.key_added_successful Security key added successfully. @@ -8698,7 +8698,7 @@ Element 3 obsolete - + Username Username @@ -8707,7 +8707,7 @@ Element 3 obsolete - + log.type.security.google_disabled Authenticator App disabled @@ -8716,7 +8716,7 @@ Element 3 obsolete - + log.type.security.u2f_removed Security key removed @@ -8725,7 +8725,7 @@ Element 3 obsolete - + log.type.security.u2f_added Security key added @@ -8734,7 +8734,7 @@ Element 3 obsolete - + log.type.security.backup_keys_reset Backup keys regenerated @@ -8743,7 +8743,7 @@ Element 3 obsolete - + log.type.security.google_enabled Authenticator App enabled @@ -8752,7 +8752,7 @@ Element 3 obsolete - + log.type.security.password_changed Password changed @@ -8761,7 +8761,7 @@ Element 3 obsolete - + log.type.security.trusted_device_reset Trusted devices resetted @@ -8770,7 +8770,7 @@ Element 3 obsolete - + log.type.collection_element_deleted Element of Collection deleted @@ -8779,7 +8779,7 @@ Element 3 obsolete - + log.type.security.password_reset Password reset @@ -8788,7 +8788,7 @@ Element 3 obsolete - + log.type.security.2fa_admin_reset Two Factor Reset by Administrator @@ -8797,7 +8797,7 @@ Element 3 obsolete - + log.type.user_not_allowed Unauthorized access attempt @@ -8806,7 +8806,7 @@ Element 3 obsolete - + log.database_updated.success Success @@ -8815,7 +8815,7 @@ Element 3 obsolete - + label_options.barcode_type.2D 2D @@ -8824,7 +8824,7 @@ Element 3 obsolete - + label_options.barcode_type.1D 1D @@ -8833,7 +8833,7 @@ Element 3 obsolete - + perm.part.parameters Parameters @@ -8842,7 +8842,7 @@ Element 3 obsolete - + perm.attachment_show_private View private attachments @@ -8851,7 +8851,7 @@ Element 3 obsolete - + perm.tools.label_scanner Label scanner @@ -8860,7 +8860,7 @@ Element 3 obsolete - + perm.self.read_profiles Read profiles @@ -8869,7 +8869,7 @@ Element 3 obsolete - + perm.self.create_profiles Create profiles @@ -8878,2449 +8878,2449 @@ Element 3 obsolete - + perm.labels.use_twig Use twig mode - + label_profile.showInDropdown Show in quick select - + group.edit.enforce_2fa Enforce Two-factor authentication (2FA) - + group.edit.enforce_2fa.help If this option is enabled, every direct member of this group, has to configure at least one second-factor for authentication. Recommended for administrative groups with much permissions. - + selectpicker.empty Nothing selected - + selectpicker.nothing_selected Nothing selected - + entity.delete.must_not_contain_parts Element "%PATH%" still contains parts! You have to move the parts, to be able to delete this element. - + entity.delete.must_not_contain_attachments Attachment type still contains attachments. Change their type, to be able to delete this attachment type. - + entity.delete.must_not_contain_prices Currency still contains price details. You have to change their currency to be able to delete this element. - + entity.delete.must_not_contain_users Users still uses this group! Change their group, to be able to delete this group. - + part.table.edit Edit - + part.table.edit.title Edit part - + part_list.action.action.title Select action - + part_list.action.action.group.favorite Favorite status - + part_list.action.action.favorite Favorite - + part_list.action.action.unfavorite Unfavorite - + part_list.action.action.group.change_field Change field - + part_list.action.action.change_category Change category - + part_list.action.action.change_footprint Change footprint - + part_list.action.action.change_manufacturer Change manufacturer - + part_list.action.action.change_unit Change part unit - + part_list.action.action.delete Delete - + part_list.action.submit Submit - + part_list.action.part_count %count% parts selected! - + company.edit.quick.website Open website - + company.edit.quick.email Send email - + company.edit.quick.phone Call phone - + company.edit.quick.fax Send fax - + company.fax_number.placeholder e.g. +49 1234 567890 - + part.edit.save_and_clone Save and clone - + validator.file_ext_not_allowed File extension not allowed for this attachment type. - + tools.reel_calc.title SMD Reel calculator - + tools.reel_calc.inner_dia Inner diameter - + tools.reel_calc.outer_dia Outer diameter - + tools.reel_calc.tape_thick Tape thickness - + tools.reel_calc.part_distance Part distance - + tools.reel_calc.update Update - + tools.reel_calc.parts_per_meter Parts per meter - + tools.reel_calc.result_length Tape length - + tools.reel_calc.result_amount Approx. parts count - + tools.reel_calc.outer_greater_inner_error Error: Outer diameter must be greater than inner diameter! - + tools.reel_calc.missing_values.error Please fill in all values! - + tools.reel_calc.load_preset Load preset - + tools.reel_calc.explanation This calculator gives you an estimation, how many parts are remaining on an SMD reel. Measure the noted the dimensions on the reel (or use some of the presets) and click "Update" to get an result. - + perm.tools.reel_calculator SMD Reel calculator - + tree.tools.tools.reel_calculator SMD Reel calculator - + user.pw_change_needed.flash Your password needs to be changed! Please set a new password. - + tree.root_node.text Root node - + part_list.action.select_null Empty element - + part_list.action.delete-title Do you really want to delete these parts? - + part_list.action.delete-message These parts and any associated information (like attachments, price information, etc.) will be deleted. This can not be undone! - + part.table.actions.success Actions finished successfully. - + attachment.edit.delete.confirm Do you really want to delete this attachment? - + filter.text_constraint.value.operator.EQ Is - + filter.text_constraint.value.operator.NEQ Is not - + filter.text_constraint.value.operator.STARTS Starts with - + filter.text_constraint.value.operator.CONTAINS Contains - + filter.text_constraint.value.operator.ENDS Ends with - + filter.text_constraint.value.operator.LIKE LIKE pattern - + filter.text_constraint.value.operator.REGEX Regular expression - + filter.number_constraint.value.operator.BETWEEN Between - + filter.number_constraint.AND and - + filter.entity_constraint.operator.EQ Is (excluding children) - + filter.entity_constraint.operator.NEQ Is not (excluding children) - + filter.entity_constraint.operator.INCLUDING_CHILDREN Is (including children) - + filter.entity_constraint.operator.EXCLUDING_CHILDREN Is not (excluding children) - + part.filter.dbId Database ID - + filter.tags_constraint.operator.ANY Any of the tags - + filter.tags_constraint.operator.ALL All the tags - + filter.tags_constraint.operator.NONE None of the tags - + part.filter.lot_count Number of lots - + part.filter.attachments_count Number of attachments - + part.filter.orderdetails_count Number of orderdetails - + part.filter.lotExpirationDate Lot expiration date - + part.filter.lotNeedsRefill Any lot needs refill - + part.filter.lotUnknwonAmount Any lot has unknown amount - + part.filter.attachmentName Attachment name - + filter.choice_constraint.operator.ANY Any of - + filter.choice_constraint.operator.NONE None of - + part.filter.amount_sum Total amount - + filter.submit Update - + filter.discard Discard changes - + filter.clear_filters Clear all filters - + filter.title Filter - + filter.parameter_value_constraint.operator.= Typ. Value = - + filter.parameter_value_constraint.operator.!= Typ. Value != - + filter.parameter_value_constraint.operator.< - Typ. Value < + - + filter.parameter_value_constraint.operator.> - Typ. Value > + ]]> - + filter.parameter_value_constraint.operator.<= - Typ. Value <= + - + filter.parameter_value_constraint.operator.>= - Typ. Value >= + =]]> - + filter.parameter_value_constraint.operator.BETWEEN Typ. Value is between - + filter.parameter_value_constraint.operator.IN_RANGE In Value range - + filter.parameter_value_constraint.operator.NOT_IN_RANGE Not in Value range - + filter.parameter_value_constraint.operator.GREATER_THAN_RANGE Greater than Value range - + filter.parameter_value_constraint.operator.GREATER_EQUAL_RANGE Greater equal than Value range - + filter.parameter_value_constraint.operator.LESS_THAN_RANGE Less than Value range - + filter.parameter_value_constraint.operator.LESS_EQUAL_RANGE Less equal than Value range - + filter.parameter_value_constraint.operator.RANGE_IN_RANGE Range is completely in Value range - + filter.parameter_value_constraint.operator.RANGE_INTERSECT_RANGE Range intersects Value range - + filter.text_constraint.value No value set - + filter.number_constraint.value1 No value set - + filter.number_constraint.value2 Maximum value - + filter.datetime_constraint.value1 No datetime set - + filter.datetime_constraint.value2 Maximum datetime - + filter.constraint.add Add constraint - + part.filter.parameters_count Number of parameters - + part.filter.lotDescription Lot description - + parts_list.search.searching_for - Searching parts with keyword <b>%keyword%</b> + %keyword%]]> - + parts_list.search_options.caption Enabled search options - + attachment.table.element_type Associated element type - + log.level.debug Debug - + log.level.info Info - + log.level.notice Notice - + log.level.warning Warning - + log.level.error Error - + log.level.critical Critical - + log.level.alert Alert - + log.level.emergency Emergency - + log.type.security Security related event - + log.type.instock_changed [LEGACY] Instock changed - + log.target_id Target element ID - + entity.info.parts_count_recursive Number of parts with this element or its sub elements - + tools.server_infos.title Server info - + permission.preset.read_only Read-Only - + permission.preset.read_only.desc Only allow read operations on data - + permission.preset.all_inherit Inherit all - + permission.preset.all_inherit.desc Set all permissions to Inherit - + permission.preset.all_forbid Forbid all - + permission.preset.all_forbid.desc Set all permissions to Forbid - + permission.preset.all_allow Allow all - + permission.preset.all_allow.desc Set all permissions to allow - + perm.server_infos Server info - + permission.preset.editor Editor - + permission.preset.editor.desc Allow changing parts and data structures - + permission.preset.admin Admin - + permission.preset.admin.desc Allow administrative actions - + permission.preset.button Apply preset - + perm.attachments.show_private Show private attachments - + perm.attachments.list_attachments Show list of all attachments - + user.edit.permission_success Permission preset applied successfully. Check if the new permissions fit your needs. - + perm.group.data Data - + part_list.action.action.group.needs_review Needs Review - + part_list.action.action.set_needs_review Set Needs Review Status - + part_list.action.action.unset_needs_review Unset Needs Review Status - + part.edit.ipn Internal Part Number (IPN) - + part.ipn.not_defined Not defined - + part.table.ipn IPN - + currency.edit.update_rate Retrieve exchange rate - + currency.edit.exchange_rate_update.unsupported_currency The currency is unsupported by the exchange rate provider. Check your exchange rate provider configuration. - + currency.edit.exchange_rate_update.generic_error Unable to retrieve the exchange rate. Check your exchange rate provider configuration. - + currency.edit.exchange_rate_updated.success Retrieved the exchange rate successfully. - + project.bom.quantity BOM Qty. - + project.bom.mountnames Mount names - + project.bom.name Name - + project.bom.comment Notes - + project.bom.part Part - + project.bom.add_entry Add entry - + part_list.action.group.projects Projects - + part_list.action.projects.add_to_project Add parts to project - + project.bom.delete.confirm Do you really want to delete this BOM entry? - + project.add_parts_to_project Add parts to project BOM - + part.info.add_part_to_project Add this part to a project - + project_bom_entry.label BOM entry - + project.edit.status Project status - + project.status.draft Draft - + project.status.planning Planning - + project.status.in_production In production - + project.status.finished Finished - + project.status.archived Archived - + part.new_build_part.error.build_part_already_exists The project already has a build part! - + project.edit.associated_build_part Associated builds part - + project.edit.associated_build_part.add Add builds part - + project.edit.associated_build.hint This part represents the builds of this project, which are stored somewhere. - + part.info.projectBuildPart.hint This part represents the builds of the following project and is associated with it - + part.is_build_part Is project builds part - + project.info.title Project info - + project.info.bom_entries_count BOM entries - + project.info.sub_projects_count Subprojects - + project.info.bom_add_parts Add BOM entries - + project.info.info.label Info - + project.info.sub_projects.label Subprojects - + project.bom.price Price - + part.info.withdraw_modal.title.withdraw Withdraw parts from lot - + part.info.withdraw_modal.title.add Add parts to lot - + part.info.withdraw_modal.title.move Move parts from lot to another lot - + part.info.withdraw_modal.amount Amount - + part.info.withdraw_modal.move_to Move to - + part.info.withdraw_modal.comment Comment - + part.info.withdraw_modal.comment.hint You can set a comment here describing why you are doing this operation (e.g. for what you need the parts). This info will be saved in the log. - + modal.close Close - + modal.submit Submit - + part.withdraw.success Added/Moved/Withdrawn parts successfully. - + perm.parts_stock Parts Stock - + perm.parts_stock.withdraw Withdraw parts from stock - + perm.parts_stock.add Add parts to stock - + perm.parts_stock.move Move parts between lots - + user.permissions_schema_updated The permission schema of your user were upgraded to the latest version. - + log.type.part_stock_changed Part Stock changed - + log.part_stock_changed.withdraw Stock withdrawn - + log.part_stock_changed.add Stock added - + log.part_stock_changed.move Stock moved - + log.part_stock_changed.comment Comment - + log.part_stock_changed.change Change - + log.part_stock_changed.move_target Move target - + tools.builtin_footprints_viewer.title Builtin footprint image gallery - + tools.builtin_footprints_viewer.hint This gallery lists all available built-in footprint images. If you want to use them in an attachment, type in the name (or a keyword) in the path field of the attachment and select the image from the dropdown select. - + tools.ic_logos.title IC logos - + part_list.action.group.labels Labels - + part_list.action.projects.generate_label Generate labels (for parts) - + part_list.action.projects.generate_label_lot Generate labels (for part lots) - + part_list.action.generate_label.empty Empty label - + project.info.builds.label Build - + project.builds.build_not_possible Build not possible: Parts not stocked - + project.builds.following_bom_entries_miss_instock The following parts have not enough stock to build this project at least once: - + project.builds.stocked stocked - + project.builds.needed needed - + project.builds.build_possible Build possible - + project.builds.number_of_builds_possible - You have enough stocked to build <b>%max_builds%</b> builds of this project. + %max_builds% builds of this project.]]> - + project.builds.check_project_status - The current project status is <b>"%project_status%"</b>. You should check if you really want to build the project with this status! + "%project_status%". You should check if you really want to build the project with this status!]]> - + project.builds.following_bom_entries_miss_instock_n You do not have enough parts stocked to build this project %number_of_builds% times. The following parts have missing instock: - + project.build.flash.invalid_input Can not build project. Check input! - + project.build.required_qty Required quantity - + project.build.btn_build Build - + project.build.help Choose from which part lots the stock to build this project should be taken (and in which amount). Check the checkbox for each BOM Entry, when you are finished withdrawing the parts, or use the top checkbox to check all boxes at once. - + project.build.buildsPartLot.new_lot Create new lot - + project.build.add_builds_to_builds_part Add builds to project builds part - + project.build.builds_part_lot Target lot - + project.builds.number_of_builds Build amount - + project.builds.no_stocked_builds Number of stocked builds - + user.change_avatar.label Change profile picture - + user_settings.change_avatar.label Change profile picture - + user_settings.remove_avatar.label Remove profile picture - + part.edit.name.category_hint Hint from category - + category.edit.partname_regex.placeholder e.g "/Capacitor \d+ nF/i" - + category.edit.partname_regex.help A PCRE-compatible regular expression, which a part name have to match. - + entity.select.add_hint - Use -> to create nested structures, e.g. "Node 1->Node 1.1" + to create nested structures, e.g. "Node 1->Node 1.1"]]> - + entity.select.group.new_not_added_to_DB New (not added to DB yet) - + part.edit.save_and_new Save and create new empty part - + homepage.first_steps.title First steps - + homepage.first_steps.introduction - Your database is still empty. You might want to read the <a href="%url%">documentation</a> or start to creating the following data structures: + documentation or start to creating the following data structures:]]> - + homepage.first_steps.create_part - Or you can directly <a href="%url%">create a new part</a>. + create a new part.]]> - + homepage.first_steps.hide_hint This box will hide as soon as you have created your first part. - + homepage.forum.text - For questions about Part-DB use the <a href="%href%" class="link-external" target="_blank">discussion forum</a> + discussion forum]]> - + log.element_edited.changed_fields.category Category - + log.element_edited.changed_fields.footprint Footprint - + log.element_edited.changed_fields.manufacturer Manufacturer - + log.element_edited.changed_fields.value_typical typ. value - + log.element_edited.changed_fields.pw_reset_expires Password reset - + log.element_edited.changed_fields.comment Notes - + log.element_edited.changed_fields.supplierpartnr Supplier part number - + log.element_edited.changed_fields.supplier_product_url Link to offer - + log.element_edited.changed_fields.price Price - + log.element_edited.changed_fields.min_discount_quantity Minimum discount amount - + log.element_edited.changed_fields.original_filename Original filename - + log.element_edited.changed_fields.path Filepath - + log.element_edited.changed_fields.description Description - + log.element_edited.changed_fields.manufacturing_status Manufacturing status - + log.element_edited.changed_fields.options.barcode_type Barcode type - + log.element_edited.changed_fields.status Status - + log.element_edited.changed_fields.quantity BOM Qty. - + log.element_edited.changed_fields.mountnames Mountnames - + log.element_edited.changed_fields.name Name - + log.element_edited.changed_fields.part Part - + log.element_edited.changed_fields.price_currency Currency of price - + log.element_edited.changed_fields.partname_hint Part name hint - + log.element_edited.changed_fields.partname_regex Name filter - + log.element_edited.changed_fields.disable_footprints Disable footprints - + log.element_edited.changed_fields.disable_manufacturers Disable manufacturers - + log.element_edited.changed_fields.disable_autodatasheets Disable automatic datasheet links - + log.element_edited.changed_fields.disable_properties Disable properties - + log.element_edited.changed_fields.default_description Default description - + log.element_edited.changed_fields.default_comment Default notes - + log.element_edited.changed_fields.filetype_filter Allowed file extensions - + log.element_edited.changed_fields.not_selectable Not selected - + log.element_edited.changed_fields.parent Parent element - + log.element_edited.changed_fields.shipping_costs Shipping costs - + log.element_edited.changed_fields.default_currency Default currency - + log.element_edited.changed_fields.address Address - + log.element_edited.changed_fields.phone_number Phone number - + log.element_edited.changed_fields.fax_number Fax number - + log.element_edited.changed_fields.email_address Email - + log.element_edited.changed_fields.website Website - + log.element_edited.changed_fields.auto_product_url Product URL - + log.element_edited.changed_fields.is_full Storelocation full - + log.element_edited.changed_fields.limit_to_existing_parts Limit to existing parts - + log.element_edited.changed_fields.only_single_part Only single part - + log.element_edited.changed_fields.storage_type Storage type - + log.element_edited.changed_fields.footprint_3d 3D model - + log.element_edited.changed_fields.master_picture_attachment Preview image - + log.element_edited.changed_fields.exchange_rate Exchange rate - + log.element_edited.changed_fields.iso_code Exchange rate - + log.element_edited.changed_fields.unit Unit symbol - + log.element_edited.changed_fields.is_integer Is integer - + log.element_edited.changed_fields.use_si_prefix Use SI prefix - + log.element_edited.changed_fields.options.width Width - + log.element_edited.changed_fields.options.height Height - + log.element_edited.changed_fields.options.supported_element Target type - + log.element_edited.changed_fields.options.additional_css Additional styles (CSS) - + log.element_edited.changed_fields.options.lines Content - + log.element_edited.changed_fields.permissions.data Permissions - + log.element_edited.changed_fields.disabled Disabled - + log.element_edited.changed_fields.theme Theme - + log.element_edited.changed_fields.timezone Timezone - + log.element_edited.changed_fields.language Language - + log.element_edited.changed_fields.email Email - + log.element_edited.changed_fields.department Department - + log.element_edited.changed_fields.last_name Last name - + log.element_edited.changed_fields.first_name First name - + log.element_edited.changed_fields.group Group - + log.element_edited.changed_fields.currency Preferred currency - + log.element_edited.changed_fields.enforce2FA Enforce 2FA - + log.element_edited.changed_fields.symbol Symbol - + log.element_edited.changed_fields.value_min Min. value - + log.element_edited.changed_fields.value_max Max. value - + log.element_edited.changed_fields.value_text Text value - + log.element_edited.changed_fields.show_in_table Show in table - + log.element_edited.changed_fields.attachment_type Show in table - + log.element_edited.changed_fields.needs_review Needs review - + log.element_edited.changed_fields.tags Tags - + log.element_edited.changed_fields.mass Mass - + log.element_edited.changed_fields.ipn IPN - + log.element_edited.changed_fields.favorite Favorite - + log.element_edited.changed_fields.minamount Minimum stock - + log.element_edited.changed_fields.manufacturer_product_url Link to product page - + log.element_edited.changed_fields.manufacturer_product_number MPN - + log.element_edited.changed_fields.partUnit Measuring Unit - + log.element_edited.changed_fields.expiration_date Expiration date - + log.element_edited.changed_fields.amount Amount - + log.element_edited.changed_fields.storage_location Storage location - + attachment.max_file_size Maximum file size - + user.saml_user SSO / SAML user - + user.saml_user.pw_change_hint Your user uses single sign-on (SSO). You can not change the password and 2FA settings here. Configure them on your central SSO provider instead! - + login.sso_saml_login Single Sign-On Login (SSO) - + login.local_login_hint The form below is only for log in with a local user. If you want to log in via single sign-on, press the button above. - + part_list.action.action.export Export parts - + part_list.action.export_json Export to JSON - + part_list.action.export_csv Export to CSV - + part_list.action.export_yaml Export to YAML - + part_list.action.export_xml Export to XML - + parts.import.title Import parts - + parts.import.errors.title Import violations - + parts.import.flash.error Errors during import. This is most likely caused by some invalid data. - + parts.import.format.auto Automatic (based on file extension) - + parts.import.flash.error.unknown_format Could not determine the format from the given file! - + parts.import.flash.error.invalid_file File invalid / malformatted. Please check that you have selected the right format! - + parts.import.part_category.label Category override - + parts.import.part_category.help If you select a value here, all imported parts will be assigned to this category. No matter what was set in the data. - + import.create_unknown_datastructures Create unknown datastructures - + import.create_unknown_datastructures.help If this is selected, datastructures (like categories, footprints, etc.) which does not exist in the database yet, will be automatically created. If this is not selected, only existing data structures will be used, and if no matching data structure is found, the part will get assigned nothing - + import.path_delimiter Path delimiter - + import.path_delimiter.help The delimiter used to mark different levels in data structure pathes like category, footprint, etc. - + parts.import.help_documentation - See the <a href="%link%">documentation</a> for more information on the file format. + documentation for more information on the file format.]]> - + parts.import.help You can import parts from existing files with this tool. The parts will be directly written to database, so please check your file beforehand for correct content before uploading it here. - + parts.import.flash.success Part import successful! - + parts.import.errors.imported_entities Imported parts - + perm.import Import data - + parts.import.part_needs_review.label Mark all imported parts as "Needs review" - + parts.import.part_needs_review.help If this option is selected, then all parts will be marked as "Needs review", no matter what was set in the data. - + project.bom_import.flash.success Imported %count% BOM entries successfully. - + project.bom_import.type Type - + project.bom_import.type.kicad_pcbnew KiCAD Pcbnew BOM (CSV file) - + project.bom_import.clear_existing_bom Clear existing BOM entries before importing - + project.bom_import.clear_existing_bom.help Selecting this option will remove all existing BOM entries in the project and overwrite them with the imported BOM file! - + project.bom_import.flash.invalid_file File could not be imported. Please check that you have selected the right file type. Error message: %message% - + project.bom_import.flash.invalid_entries Validation error! Please check your data! - + project.import_bom Import BOM for project - + project.edit.bom.import_bom Import BOM - + measurement_unit.new New Measurement Unit - + measurement_unit.edit Edit Measurement Unit - + user.aboutMe.label About Me - + storelocation.owner.label Owner - + storelocation.part_owner_must_match.label Part Lot owner must match storage location owner - + part_lot.owner Owner - + part_lot.owner.help Only the owner can withdraw or add stock to this lot. - + log.element_edited.changed_fields.owner Owner - + log.element_edited.changed_fields.instock_unknown Amount unknown - + log.element_edited.changed_fields.needs_refill Refill needed - + part.withdraw.access_denied Not allowed to do the desired action. Please check your permissions and the owner of the part lots. - + part.info.amount.less_than_desired Less than desired - + log.cli_user CLI user - + log.element_edited.changed_fields.part_owner_must_match Part owner must match storage location owner - + part.filter.lessThanDesired - In stock less than desired (total amount < min. amount) + - + part.filter.lotOwner Lot owner - + user.show_email_on_profile.label Show email on public profile page - + log.details.title Log details - + log.user_login.login_from_ip Login from IP address - + log.user_login.ip_anonymize_hint If the last digits of the IP address are missing, then the GPDR mode is enabled, in which IP addresses are anynomized. - + log.user_not_allowed.unauthorized_access_attempt_to Unauthorized access attempt to page - + log.user_not_allowed.hint The request was blocked. No action should be required. - + log.no_comment No comment - + log.element_changed.field Field - + log.element_changed.data_before Data before change - + error_table.error An error occured during your request. - + part.table.invalid_regex Invalid regular expression (regex) - + log.element_changed.data_after Data after change - + log.element_changed.diff Difference - + log.undo.undo.short Undo - + log.undo.revert.short Revert to this timestamp - + log.view_version View version - + log.undo.undelete.short Undelete - + log.element_edited.changed_fields.id ID - + log.element_edited.changed_fields.id_owner Owner - + log.element_edited.changed_fields.parent_id Parent - + log.details.delete_entry Delete log entry - + log.delete.message.title Do you really want to delete the log entry? - + log.delete.message If this is an element history entry, this breaks the element history! This can lead to unexpected results when using the time travel function. - + log.collection_deleted.on_collection on Collection - + log.element_edited.changed_fields.attachments Attachments @@ -11392,37 +11392,37 @@ Element 3 - + perm.users.impersonate Impersonate other users - + user.impersonated_by.label Impersonated by - + user.stop_impersonation Stop impersonation - + user.impersonate.btn Impersonate - + user.impersonate.confirm.title Do you really want to impersonate this user? - + user.impersonate.confirm.message This will be logged. You should only do this with a good reason. @@ -11430,151 +11430,151 @@ Please note, that you can not impersonate a disabled user. If you try you will g - + log.type.security.user_impersonated User impersonated - + info_providers.providers_list.title Information providers - + info_providers.providers_list.active Active - + info_providers.providers_list.disabled Disabled - + info_providers.capabilities.basic Basic - + info_providers.capabilities.footprint Footprint - + info_providers.capabilities.picture Picture - + info_providers.capabilities.datasheet Datasheets - + info_providers.capabilities.price Prices - + part.info_provider_reference.badge The information provider used to create this part. - + part.info_provider_reference Created by Information provider - + oauth_client.connect.btn Connect OAuth - + info_providers.table.provider.label Provider - + info_providers.search.keyword Keyword - + info_providers.search.submit Search - + info_providers.search.providers.help Select the providers in which should be searched. - + info_providers.search.providers Providers - + info_providers.search.info_providers_list Show all available info providers - + info_providers.search.title Create parts from info provider - + oauth_client.flash.connection_successful Connected to OAuth application successfully! - + perm.part.info_providers Info providers - + perm.part.info_providers.create_parts Create parts from info provider - + entity.edit.alternative_names.label Alternative names - + entity.edit.alternative_names.help The alternative names given here, are used to find this element based on the results of the information providers. - + info_providers.form.help_prefix Provider @@ -11795,5 +11795,11 @@ Please note, that you can not impersonate a disabled user. If you try you will g No API tokens configured yet. + + + api_token.ends_with + Ends with + + diff --git a/translations/security.en.xlf b/translations/security.en.xlf index 43f2a92f..3ca2bee3 100644 --- a/translations/security.en.xlf +++ b/translations/security.en.xlf @@ -2,13 +2,13 @@ - + user.login_error.user_disabled Your account is disabled! Contact an administrator if you think this is wrong. - + saml.error.cannot_login_local_user_per_saml You cannot login as local user via SSO! Use your local user password instead. diff --git a/translations/validators.en.xlf b/translations/validators.en.xlf index c8ef6e3b..1f3438de 100644 --- a/translations/validators.en.xlf +++ b/translations/validators.en.xlf @@ -37,7 +37,7 @@ Part-DB1\src\Entity\UserSystem\Group.php:0 Part-DB1\src\Entity\UserSystem\User.php:0 - + part.master_attachment.must_be_picture The preview attachment must be a valid picture! @@ -82,7 +82,7 @@ src\Entity\StructuralDBElement.php:0 src\Entity\Supplier.php:0 - + structural.entity.unique_name An element with this name already exists on this level! @@ -102,7 +102,7 @@ Part-DB1\src\Entity\Parameters\StorelocationParameter.php:0 Part-DB1\src\Entity\Parameters\SupplierParameter.php:0 - + parameters.validator.min_lesser_typical Value must be lesser or equal the the typical value ({{ compared_value }}). @@ -122,7 +122,7 @@ Part-DB1\src\Entity\Parameters\StorelocationParameter.php:0 Part-DB1\src\Entity\Parameters\SupplierParameter.php:0 - + parameters.validator.min_lesser_max Value must be lesser than the maximum value ({{ compared_value }}). @@ -142,7 +142,7 @@ Part-DB1\src\Entity\Parameters\StorelocationParameter.php:0 Part-DB1\src\Entity\Parameters\SupplierParameter.php:0 - + parameters.validator.max_greater_typical Value must be greater or equal than the typical value ({{ compared_value }}). @@ -152,7 +152,7 @@ Part-DB1\src\Entity\UserSystem\User.php:0 Part-DB1\src\Entity\UserSystem\User.php:0 - + validator.user.username_already_used A user with this name is already exisiting @@ -162,7 +162,7 @@ Part-DB1\src\Entity\UserSystem\User.php:0 Part-DB1\src\Entity\UserSystem\User.php:0 - + user.invalid_username The username must contain only letters, numbers, underscores, dots, pluses or minuses! @@ -171,7 +171,7 @@ obsolete - + validator.noneofitschild.self An element can not be its own parent! @@ -180,139 +180,139 @@ obsolete - + validator.noneofitschild.children You can not assign children element as parent (This would cause loops)! - + validator.select_valid_category Please select a valid category! - + validator.part_lot.only_existing Can not add new parts to this location as it is marked as "Only Existing" - + validator.part_lot.location_full.no_increase Location is full. Amount can not be increased (new value must be smaller than {{ old_amount }}). - + validator.part_lot.location_full Location is full. Can not add new parts to it. - + validator.part_lot.single_part This location can only contain a single part and it is already full! - + validator.attachment.must_not_be_null You must select an attachment type! - + validator.orderdetail.supplier_must_not_be_null You must select an supplier! - + validator.measurement_unit.use_si_prefix_needs_unit To enable SI prefixes, you have to set a unit symbol! - + part.ipn.must_be_unique The internal part number must be unique. {{ value }} is already in use! - + validator.project.bom_entry.name_or_part_needed You have to choose a part for a part BOM entry or set a name for a non-part BOM entry. - + project.bom_entry.name_already_in_bom There is already an BOM entry with this name! - + project.bom_entry.part_already_in_bom This part already exists in the BOM! - + project.bom_entry.mountnames_quantity_mismatch The number of mountnames has to match the BOMs quantity! - + project.bom_entry.can_not_add_own_builds_part You can not add a project's own builds part to the BOM. - + project.bom_has_to_include_all_subelement_parts The project BOM has to include all subprojects builds parts. Part %part_name% of project %project_name% missing! - + project.bom_entry.price_not_allowed_on_parts Prices are not allowed on BOM entries associated with a part. Define the price on the part instead. - + validator.project_build.lot_bigger_than_needed You have selected more quantity to withdraw than needed! Remove unnecessary quantity. - + validator.project_build.lot_smaller_than_needed You have selected less quantity to withdraw than needed for the build! Add additional quantity. - + part.name.must_match_category_regex The part name does not match the regular expression stated by the category: %regex% - + validator.attachment.name_not_blank Set a value here, or upload a file to automatically use its filename as name for the attachment. - + validator.part_lot.owner_must_match_storage_location_owner The owner of this lot must match the owner of the selected storage location (%owner_name%)! - + validator.part_lot.owner_must_not_be_anonymous A lot owner must not be the anonymous user! From c7a02ae870df5eaaeb53e2ad00b067c026a3473e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 2 Oct 2023 23:36:14 +0200 Subject: [PATCH 53/62] Added tests for token API authentication --- src/DataFixtures/APITokenFixtures.php | 97 +++++++++++++++++++++ src/DataFixtures/UserFixtures.php | 3 + src/Entity/UserSystem/User.php | 1 + tests/API/APITokenAuthenticationTest.php | 103 +++++++++++++++++++++++ 4 files changed, 204 insertions(+) create mode 100644 src/DataFixtures/APITokenFixtures.php create mode 100644 tests/API/APITokenAuthenticationTest.php diff --git a/src/DataFixtures/APITokenFixtures.php b/src/DataFixtures/APITokenFixtures.php new file mode 100644 index 00000000..4bcf3a60 --- /dev/null +++ b/src/DataFixtures/APITokenFixtures.php @@ -0,0 +1,97 @@ +. + */ + +declare(strict_types=1); + + +namespace App\DataFixtures; + +use App\Entity\UserSystem\ApiToken; +use App\Entity\UserSystem\ApiTokenLevel; +use App\Entity\UserSystem\User; +use Doctrine\Bundle\FixturesBundle\Fixture; +use Doctrine\Common\DataFixtures\DependentFixtureInterface; +use Doctrine\Persistence\ObjectManager; + +class APITokenFixtures extends Fixture implements DependentFixtureInterface +{ + public const TOKEN_READONLY = 'tcp_readonly'; + public const TOKEN_EDIT = 'tcp_edit'; + public const TOKEN_ADMIN = 'tcp_admin'; + public const TOKEN_FULL = 'tcp_full'; + public const TOKEN_EXPIRED = 'tcp_expired'; + + public function load(ObjectManager $manager): void + { + /** @var User $admin_user */ + $admin_user = $this->getReference(UserFixtures::ADMIN); + + $read_only_token = new ApiToken(); + $read_only_token->setUser($admin_user); + $read_only_token->setLevel(ApiTokenLevel::READ_ONLY); + $read_only_token->setName('read-only'); + $this->setTokenSecret($read_only_token, self::TOKEN_READONLY); + $manager->persist($read_only_token); + + $editor_token = new ApiToken(); + $editor_token->setUser($admin_user); + $editor_token->setLevel(ApiTokenLevel::EDIT); + $editor_token->setName('edit'); + $this->setTokenSecret($editor_token, self::TOKEN_EDIT); + $manager->persist($editor_token); + + $admin_token = new ApiToken(); + $admin_token->setUser($admin_user); + $admin_token->setLevel(ApiTokenLevel::ADMIN); + $admin_token->setName('admin'); + $this->setTokenSecret($admin_token, self::TOKEN_ADMIN); + $manager->persist($admin_token); + + $full_token = new ApiToken(); + $full_token->setUser($admin_user); + $full_token->setLevel(ApiTokenLevel::FULL); + $full_token->setName('full'); + $this->setTokenSecret($full_token, self::TOKEN_FULL); + $manager->persist($full_token); + + $expired_token = new ApiToken(); + $expired_token->setUser($admin_user); + $expired_token->setLevel(ApiTokenLevel::FULL); + $expired_token->setName('expired'); + $expired_token->setValidUntil(new \DateTimeImmutable('-1 day')); + $this->setTokenSecret($expired_token, self::TOKEN_EXPIRED); + $manager->persist($expired_token); + + $manager->flush(); + } + + private function setTokenSecret(ApiToken $token, string $secret): void + { + //Access private property + $reflection = new \ReflectionClass($token); + $property = $reflection->getProperty('token'); + $property->setValue($token, $secret); + } + + public function getDependencies(): array + { + return [UserFixtures::class]; + } +} \ No newline at end of file diff --git a/src/DataFixtures/UserFixtures.php b/src/DataFixtures/UserFixtures.php index d34a83cd..0571a4b0 100644 --- a/src/DataFixtures/UserFixtures.php +++ b/src/DataFixtures/UserFixtures.php @@ -31,6 +31,8 @@ use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; class UserFixtures extends Fixture implements DependentFixtureInterface { + public const ADMIN = 'user-admin'; + public function __construct(protected UserPasswordHasherInterface $encoder, protected EntityManagerInterface $em) { } @@ -50,6 +52,7 @@ class UserFixtures extends Fixture implements DependentFixtureInterface $admin->setNeedPwChange(false); $admin->setGroup($this->getReference(GroupFixtures::ADMINS)); $manager->persist($admin); + $this->addReference(self::ADMIN, $admin); $user = new User(); $user->setName('user'); diff --git a/src/Entity/UserSystem/User.php b/src/Entity/UserSystem/User.php index e50a90ba..0404b406 100644 --- a/src/Entity/UserSystem/User.php +++ b/src/Entity/UserSystem/User.php @@ -1023,6 +1023,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe */ public function addApiToken(ApiToken $apiToken): void { + $apiToken->setUser($this); $this->api_tokens->add($apiToken); } diff --git a/tests/API/APITokenAuthenticationTest.php b/tests/API/APITokenAuthenticationTest.php new file mode 100644 index 00000000..3e1ca599 --- /dev/null +++ b/tests/API/APITokenAuthenticationTest.php @@ -0,0 +1,103 @@ +. + */ + +declare(strict_types=1); + + +namespace API; + +use ApiPlatform\Symfony\Bundle\Test\ApiTestCase; +use App\DataFixtures\APITokenFixtures; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; +use ApiPlatform\Symfony\Bundle\Test\Client; +class APITokenAuthenticationTest extends ApiTestCase +{ + public function testUnauthenticated(): void + { + self::ensureKernelShutdown(); + $client = static::createClient(); + $client->request('GET', '/api/parts'); + self::assertResponseStatusCodeSame(401); + } + + public function testExpiredToken(): void + { + self::ensureKernelShutdown(); + $client = $this->createClientWithCredentials(APITokenFixtures::TOKEN_EXPIRED); + $client->request('GET', '/api/parts'); + self::assertResponseStatusCodeSame(401); + } + + public function testReadOnlyToken(): void + { + self::ensureKernelShutdown(); + $client = $this->createClientWithCredentials(APITokenFixtures::TOKEN_READONLY); + + //Read should be possible + $client->request('GET', '/api/parts'); + self::assertResponseIsSuccessful(); + + //Trying to list all users and create a new footprint should fail + $client->request('GET', '/api/users'); + self::assertResponseStatusCodeSame(403); + + $client->request('POST', '/api/footprints', ['json' => ['name' => 'post test']]); + self::assertResponseStatusCodeSame(403); + } + + public function testEditToken(): void + { + self::ensureKernelShutdown(); + $client = $this->createClientWithCredentials(APITokenFixtures::TOKEN_EDIT); + + //Read should be possible + $client->request('GET', '/api/parts'); + self::assertResponseIsSuccessful(); + + //Trying to list all users + $client->request('GET', '/api/users'); + self::assertResponseStatusCodeSame(403); + + $client->request('POST', '/api/footprints', ['json' => ['name' => 'post test']]); + self::assertResponseIsSuccessful(); + } + + public function testAdminToken(): void + { + self::ensureKernelShutdown(); + $client = $this->createClientWithCredentials(APITokenFixtures::TOKEN_ADMIN ); + + //Read should be possible + $client->request('GET', '/api/parts'); + self::assertResponseIsSuccessful(); + + //Trying to list all users + $client->request('GET', '/api/users'); + self::assertResponseIsSuccessful(); + + $client->request('POST', '/api/footprints', ['json' => ['name' => 'post test']]); + self::assertResponseIsSuccessful(); + } + + protected function createClientWithCredentials(string $token): Client + { + return static::createClient([], ['headers' => ['authorization' => 'Bearer '.$token]]); + } +} \ No newline at end of file From 3df47ed748f01baa31a481de4439a8819fea1e7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Tue, 3 Oct 2023 16:29:04 +0200 Subject: [PATCH 54/62] Added filters to various endpoints to allow searching for parts --- docs/api/intro.md | 19 +++++ src/ApiPlatform/Filter/LikeFilter.php | 80 ++++++++++++++++++++ src/Entity/Attachments/Attachment.php | 7 ++ src/Entity/Attachments/AttachmentType.php | 6 ++ src/Entity/Parameters/AbstractParameter.php | 9 +++ src/Entity/Parts/Category.php | 7 ++ src/Entity/Parts/Footprint.php | 7 ++ src/Entity/Parts/Manufacturer.php | 6 ++ src/Entity/Parts/MeasurementUnit.php | 6 ++ src/Entity/Parts/Part.php | 10 +++ src/Entity/Parts/PartLot.php | 10 +++ src/Entity/Parts/StorageLocation.php | 6 ++ src/Entity/Parts/Supplier.php | 6 ++ src/Entity/PriceInformations/Currency.php | 6 ++ src/Entity/PriceInformations/Orderdetail.php | 9 +++ src/Entity/PriceInformations/Pricedetail.php | 3 + src/Entity/UserSystem/User.php | 7 +- 17 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 src/ApiPlatform/Filter/LikeFilter.php diff --git a/docs/api/intro.md b/docs/api/intro.md index ddb37a44..94d410ab 100644 --- a/docs/api/intro.md +++ b/docs/api/intro.md @@ -85,6 +85,25 @@ To change the size of the pages (the number of items in a single page) use the ` See [API Platform docs](https://api-platform.com/docs/core/pagination) for more infos. +## Filtering results / Searching + +When retrieving a list of entities, you can restrict the results by various filters. Almost all entities have a search filter, +which allows you to only include entities, which (text) fields match the given search term: For example if you only want to +get parts, with the Name "BC547", you can use `/api/parts.jsonld?name=BC547`. You can use `%` as wildcard for multiple characters +in the search term (Be sure to properly encode the search term, if you use special characters). For example if you want to get all parts, +whose name starts with "BC", you can use `/api/parts.jsonld?name=BC%25` (the `%25` is the url encoded version of `%`). + +There are other filters available for some entities, allowing you to search on other fields, or restricting the results +by numeric values or dates. See the endpoint documentation for the available filters. + +## Ordering results + +When retrieving a list of entities, you can order the results by various fields using the `order` query parameter. +For example if you want to get all parts ordered by their name, you can use `/api/parts/?order[name]=asc`. You can use +this parameter multiple times to order by multiple fields. + +See the endpoint documentation for the available fields to order by. + ## Property filter Sometimes you only want to get a subset of the properties of an entity, for example when you only need the name of a part, but not all the other properties. diff --git a/src/ApiPlatform/Filter/LikeFilter.php b/src/ApiPlatform/Filter/LikeFilter.php new file mode 100644 index 00000000..fd94a50a --- /dev/null +++ b/src/ApiPlatform/Filter/LikeFilter.php @@ -0,0 +1,80 @@ +. + */ + +declare(strict_types=1); + + +namespace App\ApiPlatform\Filter; + +use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter; +use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; +use ApiPlatform\Metadata\Operation; +use Doctrine\ORM\QueryBuilder; +use Symfony\Component\PropertyInfo\Type; + +final class LikeFilter extends AbstractFilter +{ + + protected function filterProperty( + string $property, + $value, + QueryBuilder $queryBuilder, + QueryNameGeneratorInterface $queryNameGenerator, + string $resourceClass, + Operation $operation = null, + array $context = [] + ): void { + // Otherwise filter is applied to order and page as well + if ( + !$this->isPropertyEnabled($property, $resourceClass) || + !$this->isPropertyMapped($property, $resourceClass) + ) { + return; + } + $parameterName = $queryNameGenerator->generateParameterName($property); // Generate a unique parameter name to avoid collisions with other filters + $queryBuilder + ->andWhere(sprintf('o.%s LIKE :%s', $property, $parameterName)) + ->setParameter($parameterName, $value); + } + + public function getDescription(string $resourceClass): array + { + if (!$this->properties) { + return []; + } + + $description = []; + foreach ($this->properties as $property => $strategy) { + $description["$property"] = [ + 'property' => $property, + 'type' => Type::BUILTIN_TYPE_STRING, + 'required' => false, + 'description' => 'Filter using a LIKE SQL expression. Use % as wildcard for multiple characters and _ for single characters. For example, to search for all items containing foo, use foo. To search for all items starting with foo, use foo%. To search for all items ending with foo, use %foo', + 'openapi' => [ + 'example' => '', + 'allowReserved' => false,// if true, query parameters will be not percent-encoded + 'allowEmptyValue' => true, + 'explode' => false, // to be true, the type must be Type::BUILTIN_TYPE_ARRAY, ?product=blue,green will be ?product=blue&product=green + ], + ]; + } + return $description; + } +} \ No newline at end of file diff --git a/src/Entity/Attachments/Attachment.php b/src/Entity/Attachments/Attachment.php index 19810cc4..ae5c8b82 100644 --- a/src/Entity/Attachments/Attachment.php +++ b/src/Entity/Attachments/Attachment.php @@ -22,6 +22,9 @@ declare(strict_types=1); namespace App\Entity\Attachments; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; +use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Delete; @@ -30,6 +33,7 @@ use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; use App\ApiPlatform\DocumentedAPIProperty; +use App\ApiPlatform\Filter\LikeFilter; use App\Repository\AttachmentRepository; use App\EntityListeners\AttachmentDeleteListener; use Doctrine\DBAL\Types\Types; @@ -79,6 +83,9 @@ use LogicException; example: '/media/part/2/bc547-6508afa5a79c8.pdf')] #[DocumentedAPIProperty(schemaName: 'Attachment-Read', property: 'thumbnail_url', type: 'string', nullable: true, description: 'The URL to a thumbnail version of this file. This only exists for internal picture attachments.')] +#[ApiFilter(LikeFilter::class, properties: ["name"])] +#[ApiFilter(DateFilter::class, strategy: DateFilter::EXCLUDE_NULL)] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] abstract class Attachment extends AbstractNamedDBElement { /** diff --git a/src/Entity/Attachments/AttachmentType.php b/src/Entity/Attachments/AttachmentType.php index c6791d90..3afe0192 100644 --- a/src/Entity/Attachments/AttachmentType.php +++ b/src/Entity/Attachments/AttachmentType.php @@ -22,6 +22,8 @@ declare(strict_types=1); namespace App\Entity\Attachments; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\ApiResource; @@ -32,6 +34,7 @@ use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\Filter\LikeFilter; use App\Entity\Parts\Footprint; use App\Repository\StructuralDBElementRepository; use Doctrine\DBAL\Types\Types; @@ -76,6 +79,9 @@ use Symfony\Component\Validator\Constraints as Assert; normalizationContext: ['groups' => ['footprint:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] )] #[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])] +#[ApiFilter(DateFilter::class, strategy: DateFilter::EXCLUDE_NULL)] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] class AttachmentType extends AbstractStructuralDBElement { #[ORM\OneToMany(targetEntity: AttachmentType::class, mappedBy: 'parent', cascade: ['persist'])] diff --git a/src/Entity/Parameters/AbstractParameter.php b/src/Entity/Parameters/AbstractParameter.php index 7e90a546..b8b1e169 100644 --- a/src/Entity/Parameters/AbstractParameter.php +++ b/src/Entity/Parameters/AbstractParameter.php @@ -41,12 +41,17 @@ declare(strict_types=1); namespace App\Entity\Parameters; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; +use ApiPlatform\Doctrine\Orm\Filter\RangeFilter; +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 App\ApiPlatform\Filter\LikeFilter; use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\ParameterRepository; use Doctrine\DBAL\Types\Types; @@ -84,6 +89,10 @@ use function sprintf; normalizationContext: ['groups' => ['parameter:read', 'parameter:read:standalone', 'api:basic:read'], 'openapi_definition_name' => 'Read'], denormalizationContext: ['groups' => ['parameter:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], )] +#[ApiFilter(LikeFilter::class, properties: ["name", "symbol", "unit", "group", "value_text"])] +#[ApiFilter(DateFilter::class, strategy: DateFilter::EXCLUDE_NULL)] +#[ApiFilter(RangeFilter::class, properties: ["value_min", "value_typical", "value_max"])] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] abstract class AbstractParameter extends AbstractNamedDBElement { /** diff --git a/src/Entity/Parts/Category.php b/src/Entity/Parts/Category.php index b32cbbf4..3c15b86c 100644 --- a/src/Entity/Parts/Category.php +++ b/src/Entity/Parts/Category.php @@ -22,6 +22,9 @@ declare(strict_types=1); namespace App\Entity\Parts; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; +use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\ApiResource; @@ -32,6 +35,7 @@ use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\Filter\LikeFilter; use App\Entity\Attachments\Attachment; use App\Repository\Parts\CategoryRepository; use Doctrine\DBAL\Types\Types; @@ -77,6 +81,9 @@ use Symfony\Component\Validator\Constraints as Assert; normalizationContext: ['groups' => ['category:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] )] #[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])] +#[ApiFilter(DateFilter::class, strategy: DateFilter::EXCLUDE_NULL)] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] class Category extends AbstractPartsContainingDBElement { #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')] diff --git a/src/Entity/Parts/Footprint.php b/src/Entity/Parts/Footprint.php index 8dde5931..f65f5031 100644 --- a/src/Entity/Parts/Footprint.php +++ b/src/Entity/Parts/Footprint.php @@ -22,6 +22,9 @@ declare(strict_types=1); namespace App\Entity\Parts; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; +use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\ApiResource; @@ -32,6 +35,7 @@ use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\Filter\LikeFilter; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\Parts\FootprintRepository; @@ -77,6 +81,9 @@ use Symfony\Component\Validator\Constraints as Assert; normalizationContext: ['groups' => ['footprint:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] )] #[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])] +#[ApiFilter(DateFilter::class, strategy: DateFilter::EXCLUDE_NULL)] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] class Footprint extends AbstractPartsContainingDBElement { #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] diff --git a/src/Entity/Parts/Manufacturer.php b/src/Entity/Parts/Manufacturer.php index 7d5a0b4d..14b31409 100644 --- a/src/Entity/Parts/Manufacturer.php +++ b/src/Entity/Parts/Manufacturer.php @@ -22,6 +22,8 @@ declare(strict_types=1); namespace App\Entity\Parts; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\ApiResource; @@ -32,6 +34,7 @@ use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\Filter\LikeFilter; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\Parts\ManufacturerRepository; @@ -77,6 +80,9 @@ use Symfony\Component\Validator\Constraints as Assert; normalizationContext: ['groups' => ['manufacturer:read', 'company:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] )] #[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])] +#[ApiFilter(DateFilter::class, strategy: DateFilter::EXCLUDE_NULL)] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] class Manufacturer extends AbstractCompany { #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] diff --git a/src/Entity/Parts/MeasurementUnit.php b/src/Entity/Parts/MeasurementUnit.php index 7d9e76a4..4ee4176b 100644 --- a/src/Entity/Parts/MeasurementUnit.php +++ b/src/Entity/Parts/MeasurementUnit.php @@ -22,6 +22,8 @@ declare(strict_types=1); namespace App\Entity\Parts; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\ApiResource; @@ -32,6 +34,7 @@ use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\Filter\LikeFilter; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\Parts\MeasurementUnitRepository; @@ -81,6 +84,9 @@ use Symfony\Component\Validator\Constraints as Assert; normalizationContext: ['groups' => ['measurement_unit:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] )] #[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["name", "comment", "unit"])] +#[ApiFilter(DateFilter::class, strategy: DateFilter::EXCLUDE_NULL)] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] class MeasurementUnit extends AbstractPartsContainingDBElement { /** diff --git a/src/Entity/Parts/Part.php b/src/Entity/Parts/Part.php index 36a5d989..1a52490f 100644 --- a/src/Entity/Parts/Part.php +++ b/src/Entity/Parts/Part.php @@ -22,6 +22,10 @@ declare(strict_types=1); namespace App\Entity\Parts; +use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; +use ApiPlatform\Doctrine\Orm\Filter\RangeFilter; use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\ApiResource; @@ -33,6 +37,7 @@ use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; use ApiPlatform\Serializer\Filter\PropertyFilter; use App\ApiPlatform\DocumentedAPIProperty; +use App\ApiPlatform\Filter\LikeFilter; use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\PartRepository; use Doctrine\DBAL\Types\Types; @@ -86,6 +91,11 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; denormalizationContext: ['groups' => ['part:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], )] #[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["name", "comment", "description", "ipn", "tags", "manufacturer_product_number"])] +#[ApiFilter(BooleanFilter::class, properties: ["favorite" ])] +#[ApiFilter(RangeFilter::class, properties: ["mass", "minamount"])] +#[ApiFilter(DateFilter::class, strategy: DateFilter::EXCLUDE_NULL)] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] #[DocumentedAPIProperty(schemaName: 'Part-Read', property: 'total_instock', type: 'number', nullable: false, description: 'The total amount of this part in stock (sum of all part lots).')] class Part extends AttachmentContainingDBElement diff --git a/src/Entity/Parts/PartLot.php b/src/Entity/Parts/PartLot.php index 0f5fe023..58fa2afb 100644 --- a/src/Entity/Parts/PartLot.php +++ b/src/Entity/Parts/PartLot.php @@ -22,6 +22,10 @@ declare(strict_types=1); namespace App\Entity\Parts; +use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; +use ApiPlatform\Doctrine\Orm\Filter\RangeFilter; use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Delete; @@ -30,6 +34,7 @@ use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\Filter\LikeFilter; use App\Repository\PartLotRepository; use Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractDBElement; @@ -70,6 +75,11 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; denormalizationContext: ['groups' => ['part_lot:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], )] #[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["description", "comment"])] +#[ApiFilter(DateFilter::class, strategy: DateFilter::EXCLUDE_NULL)] +#[ApiFilter(BooleanFilter::class, properties: ['instock_unknown', 'needs_refill'])] +#[ApiFilter(RangeFilter::class, properties: ['amount'])] +#[ApiFilter(OrderFilter::class, properties: ['description', 'comment', 'addedDate', 'lastModified'])] class PartLot extends AbstractDBElement implements TimeStampableInterface, NamedElementInterface { use TimestampTrait; diff --git a/src/Entity/Parts/StorageLocation.php b/src/Entity/Parts/StorageLocation.php index 4a11427c..338e77d9 100644 --- a/src/Entity/Parts/StorageLocation.php +++ b/src/Entity/Parts/StorageLocation.php @@ -22,6 +22,8 @@ declare(strict_types=1); namespace App\Entity\Parts; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\ApiResource; @@ -32,6 +34,7 @@ use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\Filter\LikeFilter; use App\Entity\Attachments\Attachment; use App\Repository\Parts\StorelocationRepository; use Doctrine\DBAL\Types\Types; @@ -77,6 +80,9 @@ use Symfony\Component\Validator\Constraints as Assert; normalizationContext: ['groups' => ['location:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] )] #[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])] +#[ApiFilter(DateFilter::class, strategy: DateFilter::EXCLUDE_NULL)] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] class StorageLocation extends AbstractPartsContainingDBElement { #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')] diff --git a/src/Entity/Parts/Supplier.php b/src/Entity/Parts/Supplier.php index 749d3a30..83289439 100644 --- a/src/Entity/Parts/Supplier.php +++ b/src/Entity/Parts/Supplier.php @@ -22,6 +22,8 @@ declare(strict_types=1); namespace App\Entity\Parts; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\ApiResource; @@ -32,6 +34,7 @@ use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\Filter\LikeFilter; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\Parts\SupplierRepository; @@ -80,6 +83,9 @@ use Symfony\Component\Validator\Constraints as Assert; normalizationContext: ['groups' => ['supplier:read', 'company:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] )] #[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])] +#[ApiFilter(DateFilter::class, strategy: DateFilter::EXCLUDE_NULL)] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] class Supplier extends AbstractCompany { #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')] diff --git a/src/Entity/PriceInformations/Currency.php b/src/Entity/PriceInformations/Currency.php index ddc17610..0713c8d7 100644 --- a/src/Entity/PriceInformations/Currency.php +++ b/src/Entity/PriceInformations/Currency.php @@ -22,6 +22,8 @@ declare(strict_types=1); namespace App\Entity\PriceInformations; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\ApiResource; @@ -32,6 +34,7 @@ use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\Filter\LikeFilter; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentTypeAttachment; use App\Entity\Parts\Footprint; @@ -83,6 +86,9 @@ use Symfony\Component\Validator\Constraints as Assert; normalizationContext: ['groups' => ['currency:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] )] #[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["name", "comment", "iso_code"])] +#[ApiFilter(DateFilter::class, strategy: DateFilter::EXCLUDE_NULL)] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] class Currency extends AbstractStructuralDBElement { final public const PRICE_SCALE = 5; diff --git a/src/Entity/PriceInformations/Orderdetail.php b/src/Entity/PriceInformations/Orderdetail.php index a7be1c13..62426597 100644 --- a/src/Entity/PriceInformations/Orderdetail.php +++ b/src/Entity/PriceInformations/Orderdetail.php @@ -23,6 +23,9 @@ declare(strict_types=1); namespace App\Entity\PriceInformations; +use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Delete; @@ -32,6 +35,7 @@ use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\Filter\LikeFilter; use Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractDBElement; use App\Entity\Base\TimestampTrait; @@ -78,6 +82,11 @@ use Symfony\Component\Validator\Constraints as Assert; normalizationContext: ['groups' => ['orderdetail:read', 'pricedetail:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] )] #[ApiFilter(PropertyFilter::class)] +#[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["supplierpartnr", "supplier_product_url"])] +#[ApiFilter(BooleanFilter::class, properties: ["obsolete"])] +#[ApiFilter(DateFilter::class, strategy: DateFilter::EXCLUDE_NULL)] +#[ApiFilter(OrderFilter::class, properties: ['supplierpartnr', 'id', 'addedDate', 'lastModified'])] class Orderdetail extends AbstractDBElement implements TimeStampableInterface, NamedElementInterface { use TimestampTrait; diff --git a/src/Entity/PriceInformations/Pricedetail.php b/src/Entity/PriceInformations/Pricedetail.php index c46c3a46..dbbfecac 100644 --- a/src/Entity/PriceInformations/Pricedetail.php +++ b/src/Entity/PriceInformations/Pricedetail.php @@ -22,6 +22,8 @@ declare(strict_types=1); namespace App\Entity\PriceInformations; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Delete; @@ -30,6 +32,7 @@ use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\Filter\LikeFilter; use Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractDBElement; use App\Entity\Base\TimestampTrait; diff --git a/src/Entity/UserSystem/User.php b/src/Entity/UserSystem/User.php index 0404b406..a6caaa0f 100644 --- a/src/Entity/UserSystem/User.php +++ b/src/Entity/UserSystem/User.php @@ -22,6 +22,8 @@ declare(strict_types=1); namespace App\Entity\UserSystem; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiProperty; @@ -30,6 +32,7 @@ use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Post; use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\Filter\LikeFilter; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\UserRepository; @@ -91,7 +94,9 @@ use Jbtronics\TFAWebauthn\Model\TwoFactorInterface as WebauthnTwoFactorInterface normalizationContext: ['groups' => ['user:read'], 'openapi_definition_name' => 'Read'], )] #[ApiFilter(PropertyFilter::class)] -#[ApiFilter(SearchFilter::class, properties: ['name' => 'exact', 'email' => 'exact'])] +#[ApiFilter(LikeFilter::class, properties: ["name", "aboutMe"])] +#[ApiFilter(DateFilter::class, strategy: DateFilter::EXCLUDE_NULL)] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] #[NoLockout()] class User extends AttachmentContainingDBElement implements UserInterface, HasPermissionsInterface, TwoFactorInterface, BackupCodeInterface, TrustedDeviceInterface, WebauthnTwoFactorInterface, PreferredProviderInterface, PasswordAuthenticatedUserInterface, SamlUserInterface From 2f958dafae5a5321d5022d577b9cd355262850c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Tue, 3 Oct 2023 16:41:03 +0200 Subject: [PATCH 55/62] Fixed PHPstan issues --- .../AddDocumentedAPIPropertiesJSONSchemaFactory.php | 6 ++++-- src/Entity/Parameters/AbstractParameter.php | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ApiPlatform/AddDocumentedAPIPropertiesJSONSchemaFactory.php b/src/ApiPlatform/AddDocumentedAPIPropertiesJSONSchemaFactory.php index 98b92a41..c13af396 100644 --- a/src/ApiPlatform/AddDocumentedAPIPropertiesJSONSchemaFactory.php +++ b/src/ApiPlatform/AddDocumentedAPIPropertiesJSONSchemaFactory.php @@ -98,6 +98,8 @@ class AddDocumentedAPIPropertiesJSONSchemaFactory implements SchemaFactoryInterf $version = $schema->getVersion(); $swagger = Schema::VERSION_SWAGGER === $version; + $propertySchema = []; + if (false === $propertyMetadata->writeable) { $propertySchema['readOnly'] = true; } @@ -115,14 +117,14 @@ class AddDocumentedAPIPropertiesJSONSchemaFactory implements SchemaFactoryInterf $propertySchema['deprecated'] = true; } - if (!isset($propertySchema['default']) && !empty($default = $propertyMetadata->default)) { + if (!empty($default = $propertyMetadata->default)) { if ($default instanceof \BackedEnum) { $default = $default->value; } $propertySchema['default'] = $default; } - if (!isset($propertySchema['example']) && !empty($example = $propertyMetadata->example)) { + if (!empty($example = $propertyMetadata->example)) { $propertySchema['example'] = $example; } diff --git a/src/Entity/Parameters/AbstractParameter.php b/src/Entity/Parameters/AbstractParameter.php index b8b1e169..015e496d 100644 --- a/src/Entity/Parameters/AbstractParameter.php +++ b/src/Entity/Parameters/AbstractParameter.php @@ -191,7 +191,7 @@ abstract class AbstractParameter extends AbstractNamedDBElement * Return a formatted string version of the values of the string. * Based on the set values it can return something like this: 34 V (12 V ... 50 V) [Text]. */ - #[Groups('parameter:read', 'full')] + #[Groups(['parameter:read', 'full'])] #[SerializedName('formatted')] public function getFormattedValue(): string { From e339b7d9f0a2bfe936485682f2a41b80893d6b5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Tue, 3 Oct 2023 20:47:37 +0200 Subject: [PATCH 56/62] Fixed issue with FixInheritanceMappingMetadataFactory --- src/ApiPlatform/FixInheritanceMappingMetadataFacory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ApiPlatform/FixInheritanceMappingMetadataFacory.php b/src/ApiPlatform/FixInheritanceMappingMetadataFacory.php index 936a4bc5..c65e57a0 100644 --- a/src/ApiPlatform/FixInheritanceMappingMetadataFacory.php +++ b/src/ApiPlatform/FixInheritanceMappingMetadataFacory.php @@ -51,7 +51,7 @@ class FixInheritanceMappingMetadataFacory implements ResourceMetadataCollectionF { //If we already have a cached value, we can return it if (isset($this->cache[$resourceClass])) { - return $this->cache[$resourceClass]; + return $this->decorated->create($this->cache[$resourceClass]); } //Check if the resourceClass is a single inheritance class, then we can use the parent class to access it From 00708608cd4bb995e6431c455be854b28419e91c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Tue, 3 Oct 2023 21:37:58 +0200 Subject: [PATCH 57/62] Added entity filter to filter part response by categories, etc. --- docs/api/intro.md | 11 ++ src/ApiPlatform/Filter/EntityFilter.php | 148 ++++++++++++++++++++++++ src/Entity/Parts/Part.php | 4 +- 3 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 src/ApiPlatform/Filter/EntityFilter.php diff --git a/docs/api/intro.md b/docs/api/intro.md index 94d410ab..aca4bc49 100644 --- a/docs/api/intro.md +++ b/docs/api/intro.md @@ -96,6 +96,17 @@ whose name starts with "BC", you can use `/api/parts.jsonld?name=BC%25` (the `%2 There are other filters available for some entities, allowing you to search on other fields, or restricting the results by numeric values or dates. See the endpoint documentation for the available filters. +## Filter by associated entities + +To get all parts with a certain category, manufacturer, etc. you can use the `category`, `manufacturer`, etc. query parameters of the `/api/parts` endpoint. +They are so called entitiy filters and accept a comma separated list of IDs of the entities you want to filter by. +For example if you want to get all parts with the category "Resistor" (Category ID 1) and "Capacitor" (Category ID 2), you can use `/api/parts.jsonld?category=1,2`. + +Suffix an id with `+` to suffix, to include all direct children categories of the given category. Use the `++` suffix to include all children categories recursively. +To get all parts with the category "Resistor" (Category ID 1) and all children categories of "Capacitor" (Category ID 2), you can use `/api/parts.jsonld?category=1,2++`. + +See the endpoint documentation for the available entity filters. + ## Ordering results When retrieving a list of entities, you can order the results by various fields using the `order` query parameter. diff --git a/src/ApiPlatform/Filter/EntityFilter.php b/src/ApiPlatform/Filter/EntityFilter.php new file mode 100644 index 00000000..a8b250ad --- /dev/null +++ b/src/ApiPlatform/Filter/EntityFilter.php @@ -0,0 +1,148 @@ +. + */ + +declare(strict_types=1); + + +namespace App\ApiPlatform\Filter; + +use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter; +use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; +use ApiPlatform\Metadata\Operation; +use App\Entity\Base\AbstractStructuralDBElement; +use App\Services\Trees\NodesListBuilder; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\QueryBuilder; +use Doctrine\Persistence\ManagerRegistry; +use Psr\Log\LoggerInterface; +use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\Serializer\NameConverter\NameConverterInterface; + +class EntityFilter extends AbstractFilter +{ + + public function __construct( + ManagerRegistry $managerRegistry, + private NodesListBuilder $nodesListBuilder, + private EntityManagerInterface $entityManager, + LoggerInterface $logger = null, + ?array $properties = null, + ?NameConverterInterface $nameConverter = null + ) { + parent::__construct($managerRegistry, $logger, $properties, $nameConverter); + } + + protected function filterProperty( + string $property, + $value, + QueryBuilder $queryBuilder, + QueryNameGeneratorInterface $queryNameGenerator, + string $resourceClass, + Operation $operation = null, + array $context = [] + ): void { + if ( + !$this->isPropertyEnabled($property, $resourceClass) || + !$this->isPropertyMapped($property, $resourceClass, true) + ) { + return; + } + + $metadata = $this->getClassMetadata($resourceClass); + $target_class = $metadata->getAssociationTargetClass($property); + //If it is not an association we can not filter the property + if (!$target_class) { + return; + } + + $elements = $this->valueToEntityArray($value, $target_class); + + $parameterName = $queryNameGenerator->generateParameterName($property); // Generate a unique parameter name to avoid collisions with other filters + $queryBuilder + ->andWhere(sprintf('o.%s IN (:%s)', $property, $parameterName)) + ->setParameter($parameterName, $elements); + } + + private function valueToEntityArray(string $value, string $target_class): array + { + //Convert value to IDs: + $elements = []; + + //Split the given value by comm + foreach (explode(',', $value) as $id) { + if (trim($id) === '') { + continue; + } + + //Check if the given value ends with a plus, then we want to include all direct children + $include_children = false; + $include_recursive = false; + if (str_ends_with($id, '++')) { //Plus Plus means include all children recursively + $id = substr($id, 0, -2); + $include_recursive = true; + } elseif (str_ends_with($id, '+')) { + $id = substr($id, 0, -1); + $include_children = true; + } + + //Get a (shallow) reference to the entitity + $element = $this->entityManager->getReference($target_class, (int) $id); + $elements[] = $element; + + //If $element is not structural we are done + if (!is_a($element, AbstractStructuralDBElement::class)) { + continue; + } + + //Get the recursive list of children + if ($include_recursive) { + $elements = array_merge($elements, $this->nodesListBuilder->getChildrenFlatList($element)); + } elseif ($include_children) { + $elements = array_merge($elements, $element->getChildren()->toArray()); + } + } + + return $elements; + } + + public function getDescription(string $resourceClass): array + { + if (!$this->properties) { + return []; + } + + $description = []; + foreach ($this->properties as $property => $strategy) { + $description["$property"] = [ + 'property' => $property, + 'type' => Type::BUILTIN_TYPE_STRING, + 'required' => false, + 'description' => 'Filter using a comma seperated list of element IDs. Use + to include all direct children and ++ to include all children recursively.', + 'openapi' => [ + 'example' => '', + 'allowReserved' => false,// if true, query parameters will be not percent-encoded + 'allowEmptyValue' => true, + 'explode' => false, // to be true, the type must be Type::BUILTIN_TYPE_ARRAY, ?product=blue,green will be ?product=blue&product=green + ], + ]; + } + return $description; + } +} \ No newline at end of file diff --git a/src/Entity/Parts/Part.php b/src/Entity/Parts/Part.php index 1a52490f..aa4a9a17 100644 --- a/src/Entity/Parts/Part.php +++ b/src/Entity/Parts/Part.php @@ -37,6 +37,7 @@ use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; use ApiPlatform\Serializer\Filter\PropertyFilter; use App\ApiPlatform\DocumentedAPIProperty; +use App\ApiPlatform\Filter\EntityFilter; use App\ApiPlatform\Filter\LikeFilter; use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\PartRepository; @@ -91,8 +92,9 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; denormalizationContext: ['groups' => ['part:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], )] #[ApiFilter(PropertyFilter::class)] +#[ApiFilter(EntityFilter::class, properties: ["category", "footprint", "manufacturer", "partUnit"])] #[ApiFilter(LikeFilter::class, properties: ["name", "comment", "description", "ipn", "tags", "manufacturer_product_number"])] -#[ApiFilter(BooleanFilter::class, properties: ["favorite" ])] +#[ApiFilter(BooleanFilter::class, properties: ["favorite" , "needs_review"])] #[ApiFilter(RangeFilter::class, properties: ["mass", "minamount"])] #[ApiFilter(DateFilter::class, strategy: DateFilter::EXCLUDE_NULL)] #[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] From 852624ae7e95a3faa8c318a728107cadf04792a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Tue, 3 Oct 2023 21:59:33 +0200 Subject: [PATCH 58/62] Added filter to filter parts by storage location --- src/ApiPlatform/Filter/EntityFilter.php | 66 +---------- src/ApiPlatform/Filter/EntityFilterHelper.php | 104 ++++++++++++++++++ .../Filter/PartStoragelocationFilter.php | 83 ++++++++++++++ src/Entity/Parts/Part.php | 2 + 4 files changed, 192 insertions(+), 63 deletions(-) create mode 100644 src/ApiPlatform/Filter/EntityFilterHelper.php create mode 100644 src/ApiPlatform/Filter/PartStoragelocationFilter.php diff --git a/src/ApiPlatform/Filter/EntityFilter.php b/src/ApiPlatform/Filter/EntityFilter.php index a8b250ad..e3972732 100644 --- a/src/ApiPlatform/Filter/EntityFilter.php +++ b/src/ApiPlatform/Filter/EntityFilter.php @@ -40,8 +40,7 @@ class EntityFilter extends AbstractFilter public function __construct( ManagerRegistry $managerRegistry, - private NodesListBuilder $nodesListBuilder, - private EntityManagerInterface $entityManager, + private readonly EntityFilterHelper $filter_helper, LoggerInterface $logger = null, ?array $properties = null, ?NameConverterInterface $nameConverter = null @@ -72,7 +71,7 @@ class EntityFilter extends AbstractFilter return; } - $elements = $this->valueToEntityArray($value, $target_class); + $elements = $this->filter_helper->valueToEntityArray($value, $target_class); $parameterName = $queryNameGenerator->generateParameterName($property); // Generate a unique parameter name to avoid collisions with other filters $queryBuilder @@ -80,69 +79,10 @@ class EntityFilter extends AbstractFilter ->setParameter($parameterName, $elements); } - private function valueToEntityArray(string $value, string $target_class): array - { - //Convert value to IDs: - $elements = []; - //Split the given value by comm - foreach (explode(',', $value) as $id) { - if (trim($id) === '') { - continue; - } - - //Check if the given value ends with a plus, then we want to include all direct children - $include_children = false; - $include_recursive = false; - if (str_ends_with($id, '++')) { //Plus Plus means include all children recursively - $id = substr($id, 0, -2); - $include_recursive = true; - } elseif (str_ends_with($id, '+')) { - $id = substr($id, 0, -1); - $include_children = true; - } - - //Get a (shallow) reference to the entitity - $element = $this->entityManager->getReference($target_class, (int) $id); - $elements[] = $element; - - //If $element is not structural we are done - if (!is_a($element, AbstractStructuralDBElement::class)) { - continue; - } - - //Get the recursive list of children - if ($include_recursive) { - $elements = array_merge($elements, $this->nodesListBuilder->getChildrenFlatList($element)); - } elseif ($include_children) { - $elements = array_merge($elements, $element->getChildren()->toArray()); - } - } - - return $elements; - } public function getDescription(string $resourceClass): array { - if (!$this->properties) { - return []; - } - - $description = []; - foreach ($this->properties as $property => $strategy) { - $description["$property"] = [ - 'property' => $property, - 'type' => Type::BUILTIN_TYPE_STRING, - 'required' => false, - 'description' => 'Filter using a comma seperated list of element IDs. Use + to include all direct children and ++ to include all children recursively.', - 'openapi' => [ - 'example' => '', - 'allowReserved' => false,// if true, query parameters will be not percent-encoded - 'allowEmptyValue' => true, - 'explode' => false, // to be true, the type must be Type::BUILTIN_TYPE_ARRAY, ?product=blue,green will be ?product=blue&product=green - ], - ]; - } - return $description; + return $this->filter_helper->getDescription($this->properties); } } \ No newline at end of file diff --git a/src/ApiPlatform/Filter/EntityFilterHelper.php b/src/ApiPlatform/Filter/EntityFilterHelper.php new file mode 100644 index 00000000..6c729e1e --- /dev/null +++ b/src/ApiPlatform/Filter/EntityFilterHelper.php @@ -0,0 +1,104 @@ +. + */ + +declare(strict_types=1); + + +namespace App\ApiPlatform\Filter; + +use App\Entity\Base\AbstractStructuralDBElement; +use App\Services\Trees\NodesListBuilder; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\PropertyInfo\Type; + +class EntityFilterHelper +{ + public function __construct(private NodesListBuilder $nodesListBuilder, + private EntityManagerInterface $entityManager) + { + + } + + public function valueToEntityArray(string $value, string $target_class): array + { + //Convert value to IDs: + $elements = []; + + //Split the given value by comm + foreach (explode(',', $value) as $id) { + if (trim($id) === '') { + continue; + } + + //Check if the given value ends with a plus, then we want to include all direct children + $include_children = false; + $include_recursive = false; + if (str_ends_with($id, '++')) { //Plus Plus means include all children recursively + $id = substr($id, 0, -2); + $include_recursive = true; + } elseif (str_ends_with($id, '+')) { + $id = substr($id, 0, -1); + $include_children = true; + } + + //Get a (shallow) reference to the entitity + $element = $this->entityManager->getReference($target_class, (int) $id); + $elements[] = $element; + + //If $element is not structural we are done + if (!is_a($element, AbstractStructuralDBElement::class)) { + continue; + } + + //Get the recursive list of children + if ($include_recursive) { + $elements = array_merge($elements, $this->nodesListBuilder->getChildrenFlatList($element)); + } elseif ($include_children) { + $elements = array_merge($elements, $element->getChildren()->toArray()); + } + } + + return $elements; + } + + public function getDescription(array $properties): array + { + if (!$properties) { + return []; + } + + $description = []; + foreach ($properties as $property => $strategy) { + $description["$property"] = [ + 'property' => $property, + 'type' => Type::BUILTIN_TYPE_STRING, + 'required' => false, + 'description' => 'Filter using a comma seperated list of element IDs. Use + to include all direct children and ++ to include all children recursively.', + 'openapi' => [ + 'example' => '', + 'allowReserved' => false,// if true, query parameters will be not percent-encoded + 'allowEmptyValue' => true, + 'explode' => false, // to be true, the type must be Type::BUILTIN_TYPE_ARRAY, ?product=blue,green will be ?product=blue&product=green + ], + ]; + } + return $description; + } +} \ No newline at end of file diff --git a/src/ApiPlatform/Filter/PartStoragelocationFilter.php b/src/ApiPlatform/Filter/PartStoragelocationFilter.php new file mode 100644 index 00000000..0e942208 --- /dev/null +++ b/src/ApiPlatform/Filter/PartStoragelocationFilter.php @@ -0,0 +1,83 @@ +. + */ + +declare(strict_types=1); + + +namespace App\ApiPlatform\Filter; + +use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter; +use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; +use ApiPlatform\Metadata\Operation; +use App\Entity\Base\AbstractStructuralDBElement; +use App\Entity\Parts\StorageLocation; +use App\Services\Trees\NodesListBuilder; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\QueryBuilder; +use Doctrine\Persistence\ManagerRegistry; +use Psr\Log\LoggerInterface; +use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\Serializer\NameConverter\NameConverterInterface; + +class PartStoragelocationFilter extends AbstractFilter +{ + + public function __construct( + ManagerRegistry $managerRegistry, + private readonly EntityFilterHelper $filter_helper, + LoggerInterface $logger = null, + ?array $properties = null, + ?NameConverterInterface $nameConverter = null + ) { + parent::__construct($managerRegistry, $logger, $properties, $nameConverter); + } + + protected function filterProperty( + string $property, + $value, + QueryBuilder $queryBuilder, + QueryNameGeneratorInterface $queryNameGenerator, + string $resourceClass, + Operation $operation = null, + array $context = [] + ): void { + //Do not check for mapping here, as we are using a virtual property + if ( + !$this->isPropertyEnabled($property, $resourceClass) + ) { + return; + } + + $elements = $this->filter_helper->valueToEntityArray($value, StorageLocation::class); + + $parameterName = $queryNameGenerator->generateParameterName($property); // Generate a unique parameter name to avoid collisions with other filters + $queryBuilder + ->leftJoin('o.partLots', 'partLots') + ->andWhere(sprintf('partLots.storage_location IN (:%s)', $parameterName)) + ->setParameter($parameterName, $elements); + } + + + + public function getDescription(string $resourceClass): array + { + return $this->filter_helper->getDescription($this->properties); + } +} \ No newline at end of file diff --git a/src/Entity/Parts/Part.php b/src/Entity/Parts/Part.php index aa4a9a17..d53ac8f6 100644 --- a/src/Entity/Parts/Part.php +++ b/src/Entity/Parts/Part.php @@ -39,6 +39,7 @@ use ApiPlatform\Serializer\Filter\PropertyFilter; use App\ApiPlatform\DocumentedAPIProperty; use App\ApiPlatform\Filter\EntityFilter; use App\ApiPlatform\Filter\LikeFilter; +use App\ApiPlatform\Filter\PartStoragelocationFilter; use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\PartRepository; use Doctrine\DBAL\Types\Types; @@ -93,6 +94,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; )] #[ApiFilter(PropertyFilter::class)] #[ApiFilter(EntityFilter::class, properties: ["category", "footprint", "manufacturer", "partUnit"])] +#[ApiFilter(PartStoragelocationFilter::class, properties: ["storage_location"])] #[ApiFilter(LikeFilter::class, properties: ["name", "comment", "description", "ipn", "tags", "manufacturer_product_number"])] #[ApiFilter(BooleanFilter::class, properties: ["favorite" , "needs_review"])] #[ApiFilter(RangeFilter::class, properties: ["mass", "minamount"])] From 0e75d7672020f7befea9a6ec69c80005476f7423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Tue, 3 Oct 2023 23:53:10 +0200 Subject: [PATCH 59/62] Added API endpoints for projects --- src/Entity/ProjectSystem/Project.php | 52 +++++++++++++- src/Entity/ProjectSystem/ProjectBOMEntry.php | 46 ++++++++++++ src/Security/Voter/BOMEntryVoter.php | 76 ++++++++++++++++++++ 3 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 src/Security/Voter/BOMEntryVoter.php diff --git a/src/Entity/ProjectSystem/Project.php b/src/Entity/ProjectSystem/Project.php index 4d79ee38..68cbc0b9 100644 --- a/src/Entity/ProjectSystem/Project.php +++ b/src/Entity/ProjectSystem/Project.php @@ -22,8 +22,22 @@ declare(strict_types=1); namespace App\Entity\ProjectSystem; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\Filter\LikeFilter; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentTypeAttachment; +use App\Entity\Parts\Category; use App\Repository\Parts\DeviceRepository; use App\Validator\Constraints\UniqueObjectCollection; use Doctrine\DBAL\Types\Types; @@ -46,6 +60,31 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; */ #[ORM\Entity(repositoryClass: DeviceRepository::class)] #[ORM\Table(name: 'projects')] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@projects.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['project:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['project:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiResource( + uriTemplate: '/projects/{id}/children.{_format}', + operations: [ + new GetCollection(openapiContext: ['summary' => 'Retrieves the children elements of a project.'], + security: 'is_granted("@projects.read")') + ], + uriVariables: [ + 'id' => new Link(fromProperty: 'children', fromClass: Project::class) + ], + normalizationContext: ['groups' => ['project:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] +)] +#[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] class Project extends AbstractStructuralDBElement { #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')] @@ -54,8 +93,13 @@ class Project extends AbstractStructuralDBElement #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] #[ORM\JoinColumn(name: 'parent_id')] + #[Groups(['project:read', 'project:write'])] + #[ApiProperty(readableLink: false, writableLink: false)] protected ?AbstractStructuralDBElement $parent = null; + #[Groups(['project:read', 'project:write'])] + protected string $comment = ''; + #[Assert\Valid] #[Groups(['extended', 'full'])] #[ORM\OneToMany(targetEntity: ProjectBOMEntry::class, mappedBy: 'project', cascade: ['persist', 'remove'], orphanRemoval: true)] @@ -70,7 +114,7 @@ class Project extends AbstractStructuralDBElement * @var string|null The current status of the project */ #[Assert\Choice(['draft', 'planning', 'in_production', 'finished', 'archived'])] - #[Groups(['extended', 'full'])] + #[Groups(['extended', 'full', 'project:read', 'project:write'])] #[ORM\Column(type: Types::STRING, length: 64, nullable: true)] protected ?string $status = null; @@ -79,12 +123,13 @@ class Project extends AbstractStructuralDBElement * @var Part|null The (optional) part that represents the builds of this project in the stock */ #[ORM\OneToOne(targetEntity: Part::class, mappedBy: 'built_project', cascade: ['persist'], orphanRemoval: true)] + #[Groups(['project:read', 'project:write'])] protected ?Part $build_part = null; #[ORM\Column(type: Types::BOOLEAN)] protected bool $order_only_missing_parts = false; - #[Groups(['simple', 'extended', 'full'])] + #[Groups(['simple', 'extended', 'full', 'project:read', 'project:write'])] #[ORM\Column(type: Types::TEXT)] protected string $description = ''; @@ -93,16 +138,19 @@ class Project extends AbstractStructuralDBElement */ #[ORM\OneToMany(targetEntity: ProjectAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['name' => 'ASC'])] + #[Groups(['project:read', 'project:write'])] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: ProjectAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] + #[Groups(['project:read', 'project:write'])] protected ?Attachment $master_picture_attachment = null; /** @var Collection */ #[ORM\OneToMany(targetEntity: ProjectParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[Groups(['project:read', 'project:write'])] protected Collection $parameters; /******************************************************************************** diff --git a/src/Entity/ProjectSystem/ProjectBOMEntry.php b/src/Entity/ProjectSystem/ProjectBOMEntry.php index 8955b1cf..8ecaa1ed 100644 --- a/src/Entity/ProjectSystem/ProjectBOMEntry.php +++ b/src/Entity/ProjectSystem/ProjectBOMEntry.php @@ -22,6 +22,18 @@ declare(strict_types=1); namespace App\Entity\ProjectSystem; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; +use ApiPlatform\Doctrine\Orm\Filter\RangeFilter; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\Filter\LikeFilter; use App\Validator\UniqueValidatableInterface; use Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractDBElement; @@ -33,6 +45,7 @@ use App\Validator\Constraints\Selectable; use Brick\Math\BigDecimal; 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; @@ -42,18 +55,46 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; #[ORM\HasLifecycleCallbacks] #[ORM\Entity] #[ORM\Table('project_bom_entries')] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)', uriTemplate: '/project_bom_entries/{id}.{_format}',), + new GetCollection(security: 'is_granted("@projects.read")', uriTemplate: '/project_bom_entries.{_format}',), + new Post(securityPostDenormalize: 'is_granted("create", object)', uriTemplate: '/project_bom_entries.{_format}',), + new Patch(security: 'is_granted("edit", object)', uriTemplate: '/project_bom_entries/{id}.{_format}',), + new Delete(security: 'is_granted("delete", object)', uriTemplate: '/project_bom_entries/{id}.{_format}',), + ], + normalizationContext: ['groups' => ['bom_entry:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['bom_entry:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiResource( + uriTemplate: '/projects/{id}/bom.{_format}', + operations: [ + new GetCollection(openapiContext: ['summary' => 'Retrieves the BOM entries of the given project.'], + security: 'is_granted("@projects.read")') + ], + uriVariables: [ + 'id' => new Link(fromProperty: 'bom_entries', fromClass: Project::class) + ], + normalizationContext: ['groups' => ['bom_entry:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] +)] +#[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["name", "comment", 'mountnames'])] +#[ApiFilter(RangeFilter::class, properties: ['quantity'])] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified', 'quantity'])] class ProjectBOMEntry extends AbstractDBElement implements UniqueValidatableInterface { use TimestampTrait; #[Assert\Positive] #[ORM\Column(type: Types::FLOAT, name: 'quantity')] + #[Groups(['bom_entry:read', 'bom_entry:write'])] protected float $quantity = 1.0; /** * @var string A comma separated list of the names, where this parts should be placed */ #[ORM\Column(type: Types::TEXT, name: 'mountnames')] + #[Groups(['bom_entry:read', 'bom_entry:write'])] protected string $mountnames = ''; /** @@ -61,12 +102,14 @@ class ProjectBOMEntry extends AbstractDBElement implements UniqueValidatableInte */ #[Assert\Expression('this.getPart() !== null or this.getName() !== null', message: 'validator.project.bom_entry.name_or_part_needed')] #[ORM\Column(type: Types::STRING, nullable: true)] + #[Groups(['bom_entry:read', 'bom_entry:write'])] protected ?string $name = null; /** * @var string An optional comment for this BOM entry */ #[ORM\Column(type: Types::TEXT)] + #[Groups(['bom_entry:read', 'bom_entry:write'])] protected string $comment = ''; /** @@ -74,6 +117,7 @@ class ProjectBOMEntry extends AbstractDBElement implements UniqueValidatableInte */ #[ORM\ManyToOne(targetEntity: Project::class, inversedBy: 'bom_entries')] #[ORM\JoinColumn(name: 'id_device')] + #[Groups(['bom_entry:read', 'bom_entry:write'])] protected ?Project $project = null; /** @@ -81,6 +125,7 @@ class ProjectBOMEntry extends AbstractDBElement implements UniqueValidatableInte */ #[ORM\ManyToOne(targetEntity: Part::class, inversedBy: 'project_bom_entries')] #[ORM\JoinColumn(name: 'id_part')] + #[Groups(['bom_entry:read', 'bom_entry:write'])] protected ?Part $part = null; /** @@ -88,6 +133,7 @@ class ProjectBOMEntry extends AbstractDBElement implements UniqueValidatableInte */ #[Assert\AtLeastOneOf([new BigDecimalPositive(), new Assert\IsNull()])] #[ORM\Column(type: 'big_decimal', precision: 11, scale: 5, nullable: true)] + #[Groups(['bom_entry:read', 'bom_entry:write'])] protected ?BigDecimal $price = null; /** diff --git a/src/Security/Voter/BOMEntryVoter.php b/src/Security/Voter/BOMEntryVoter.php new file mode 100644 index 00000000..b4783b82 --- /dev/null +++ b/src/Security/Voter/BOMEntryVoter.php @@ -0,0 +1,76 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Security\Voter; + +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\Voter; + +class BOMEntryVoter extends Voter +{ + + private const ALLOWED_ATTRIBUTES = ['read', 'view', 'edit', 'delete', 'create']; + + public function __construct(private readonly Security $security) + { + } + + protected function supports(string $attribute, mixed $subject): bool + { + return $this->supportsAttribute($attribute) && is_a($subject, ProjectBOMEntry::class); + } + + protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool + { + if (!$subject instanceof ProjectBOMEntry) { + return false; + } + + $project = $subject->getProject(); + + //Allow everything if the project was not set yet + if ($project === null) { + return true; + } + + //Entry can be read if the user has read access to the project + if ($attribute === 'read') { + return $this->security->isGranted('read', $project); + } + + //Everything else can be done if the user has edit access to the project + return $this->security->isGranted('edit', $project); + } + + public function supportsAttribute(string $attribute): bool + { + return in_array($attribute, self::ALLOWED_ATTRIBUTES, true); + } + + public function supportsType(string $subjectType): bool + { + return is_a($subjectType, ProjectBOMEntry::class, true); + } +} \ No newline at end of file From e09f60e71fa6975154de2c1a9cf18c0c836f85de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Tue, 3 Oct 2023 23:58:41 +0200 Subject: [PATCH 60/62] Allow to filter attachments API response by attachment types --- src/ApiResource/.gitignore | 0 src/Entity/Attachments/Attachment.php | 2 ++ 2 files changed, 2 insertions(+) delete mode 100644 src/ApiResource/.gitignore diff --git a/src/ApiResource/.gitignore b/src/ApiResource/.gitignore deleted file mode 100644 index e69de29b..00000000 diff --git a/src/Entity/Attachments/Attachment.php b/src/Entity/Attachments/Attachment.php index ae5c8b82..78789001 100644 --- a/src/Entity/Attachments/Attachment.php +++ b/src/Entity/Attachments/Attachment.php @@ -33,6 +33,7 @@ use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; use App\ApiPlatform\DocumentedAPIProperty; +use App\ApiPlatform\Filter\EntityFilter; use App\ApiPlatform\Filter\LikeFilter; use App\Repository\AttachmentRepository; use App\EntityListeners\AttachmentDeleteListener; @@ -84,6 +85,7 @@ use LogicException; #[DocumentedAPIProperty(schemaName: 'Attachment-Read', property: 'thumbnail_url', type: 'string', nullable: true, description: 'The URL to a thumbnail version of this file. This only exists for internal picture attachments.')] #[ApiFilter(LikeFilter::class, properties: ["name"])] +#[ApiFilter(EntityFilter::class, properties: ["attachment_type"])] #[ApiFilter(DateFilter::class, strategy: DateFilter::EXCLUDE_NULL)] #[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] abstract class Attachment extends AbstractNamedDBElement From 64c38042a0c8a4e268ea9df16538725864f167fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Wed, 4 Oct 2023 00:08:10 +0200 Subject: [PATCH 61/62] Added missing lastModified and creationDate fields to API response --- src/Entity/Attachments/Attachment.php | 6 ++++++ src/Entity/Attachments/AttachmentType.php | 10 ++++++++-- src/Entity/Parts/Category.php | 6 ++++++ src/Entity/Parts/Footprint.php | 6 ++++++ src/Entity/Parts/MeasurementUnit.php | 6 ++++++ src/Entity/Parts/Part.php | 6 ++++++ src/Entity/Parts/StorageLocation.php | 6 ++++++ src/Entity/PriceInformations/Currency.php | 8 ++++++-- src/Entity/ProjectSystem/Project.php | 6 ++++++ 9 files changed, 56 insertions(+), 4 deletions(-) diff --git a/src/Entity/Attachments/Attachment.php b/src/Entity/Attachments/Attachment.php index 78789001..61f370e4 100644 --- a/src/Entity/Attachments/Attachment.php +++ b/src/Entity/Attachments/Attachment.php @@ -156,6 +156,12 @@ abstract class Attachment extends AbstractNamedDBElement #[Groups(['attachment:read', 'attachment_write'])] protected ?AttachmentType $attachment_type = null; + #[Groups(['attachment:read'])] + protected ?\DateTimeInterface $addedDate = null; + #[Groups(['attachment:read'])] + protected ?\DateTimeInterface $lastModified = null; + + public function __construct() { //parent::__construct(); diff --git a/src/Entity/Attachments/AttachmentType.php b/src/Entity/Attachments/AttachmentType.php index 3afe0192..d4cc7b25 100644 --- a/src/Entity/Attachments/AttachmentType.php +++ b/src/Entity/Attachments/AttachmentType.php @@ -76,7 +76,7 @@ use Symfony\Component\Validator\Constraints as Assert; uriVariables: [ 'id' => new Link(fromProperty: 'children', fromClass: AttachmentType::class) ], - normalizationContext: ['groups' => ['footprint:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] + normalizationContext: ['groups' => ['attachment_type:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'] )] #[ApiFilter(PropertyFilter::class)] #[ApiFilter(LikeFilter::class, properties: ["name", "comment"])] @@ -96,7 +96,7 @@ class AttachmentType extends AbstractStructuralDBElement /** * @var string A comma separated list of file types, which are allowed for attachment files. - * Must be in the format of accept attribute + * Must be in the format of
accept attribute * (See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers). */ #[ORM\Column(type: Types::TEXT)] @@ -132,6 +132,12 @@ class AttachmentType extends AbstractStructuralDBElement #[ORM\OneToMany(targetEntity: Attachment::class, mappedBy: 'attachment_type')] protected Collection $attachments_with_type; + #[Groups(['attachment_type:read'])] + protected ?\DateTimeInterface $addedDate = null; + #[Groups(['attachment_type:read'])] + protected ?\DateTimeInterface $lastModified = null; + + public function __construct() { $this->children = new ArrayCollection(); diff --git a/src/Entity/Parts/Category.php b/src/Entity/Parts/Category.php index 3c15b86c..b1dc8c97 100644 --- a/src/Entity/Parts/Category.php +++ b/src/Entity/Parts/Category.php @@ -177,6 +177,12 @@ class Category extends AbstractPartsContainingDBElement #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] protected Collection $parameters; + #[Groups(['category:read'])] + protected ?\DateTimeInterface $addedDate = null; + #[Groups(['category:read'])] + protected ?\DateTimeInterface $lastModified = null; + + public function getPartnameHint(): string { return $this->partname_hint; diff --git a/src/Entity/Parts/Footprint.php b/src/Entity/Parts/Footprint.php index f65f5031..2cdf9fba 100644 --- a/src/Entity/Parts/Footprint.php +++ b/src/Entity/Parts/Footprint.php @@ -129,6 +129,12 @@ class Footprint extends AbstractPartsContainingDBElement #[Groups(['footprint:read', 'footprint:write'])] protected Collection $parameters; + #[Groups(['footprint:read'])] + protected ?\DateTimeInterface $addedDate = null; + #[Groups(['footprint:read'])] + protected ?\DateTimeInterface $lastModified = null; + + /**************************************** * Getters ****************************************/ diff --git a/src/Entity/Parts/MeasurementUnit.php b/src/Entity/Parts/MeasurementUnit.php index 4ee4176b..f9f45ca5 100644 --- a/src/Entity/Parts/MeasurementUnit.php +++ b/src/Entity/Parts/MeasurementUnit.php @@ -150,6 +150,12 @@ class MeasurementUnit extends AbstractPartsContainingDBElement #[Groups(['measurement_unit:read', 'measurement_unit:write'])] protected Collection $parameters; + #[Groups(['measurement_unit:read'])] + protected ?\DateTimeInterface $addedDate = null; + #[Groups(['measurement_unit:read'])] + protected ?\DateTimeInterface $lastModified = null; + + /** * @return string */ diff --git a/src/Entity/Parts/Part.php b/src/Entity/Parts/Part.php index d53ac8f6..fa01d212 100644 --- a/src/Entity/Parts/Part.php +++ b/src/Entity/Parts/Part.php @@ -150,6 +150,12 @@ class Part extends AttachmentContainingDBElement #[Groups(['part:read', 'part:write'])] protected ?Attachment $master_picture_attachment = null; + #[Groups(['part:read'])] + protected ?\DateTimeInterface $addedDate = null; + #[Groups(['part:read'])] + protected ?\DateTimeInterface $lastModified = null; + + public function __construct() { $this->attachments = new ArrayCollection(); diff --git a/src/Entity/Parts/StorageLocation.php b/src/Entity/Parts/StorageLocation.php index 338e77d9..3a7847d9 100644 --- a/src/Entity/Parts/StorageLocation.php +++ b/src/Entity/Parts/StorageLocation.php @@ -164,6 +164,12 @@ class StorageLocation extends AbstractPartsContainingDBElement #[Groups(['location:read', 'location:write'])] protected ?Attachment $master_picture_attachment = null; + #[Groups(['location:read'])] + protected ?\DateTimeInterface $addedDate = null; + #[Groups(['location:read'])] + protected ?\DateTimeInterface $lastModified = null; + + /******************************************************************************** * * Getters diff --git a/src/Entity/PriceInformations/Currency.php b/src/Entity/PriceInformations/Currency.php index 0713c8d7..a108feeb 100644 --- a/src/Entity/PriceInformations/Currency.php +++ b/src/Entity/PriceInformations/Currency.php @@ -36,8 +36,6 @@ use ApiPlatform\Metadata\Post; use ApiPlatform\Serializer\Filter\PropertyFilter; use App\ApiPlatform\Filter\LikeFilter; use App\Entity\Attachments\Attachment; -use App\Entity\Attachments\AttachmentTypeAttachment; -use App\Entity\Parts\Footprint; use App\Repository\CurrencyRepository; use Doctrine\DBAL\Types\Types; use App\Entity\Attachments\CurrencyAttachment; @@ -152,6 +150,12 @@ class Currency extends AbstractStructuralDBElement #[ORM\OneToMany(targetEntity: Pricedetail::class, mappedBy: 'currency')] protected Collection $pricedetails; + #[Groups(['currency:read'])] + protected ?\DateTimeInterface $addedDate = null; + #[Groups(['currency:read'])] + protected ?\DateTimeInterface $lastModified = null; + + public function __construct() { $this->children = new ArrayCollection(); diff --git a/src/Entity/ProjectSystem/Project.php b/src/Entity/ProjectSystem/Project.php index 68cbc0b9..81b1fc09 100644 --- a/src/Entity/ProjectSystem/Project.php +++ b/src/Entity/ProjectSystem/Project.php @@ -153,6 +153,12 @@ class Project extends AbstractStructuralDBElement #[Groups(['project:read', 'project:write'])] protected Collection $parameters; + #[Groups(['project:read'])] + protected ?\DateTimeInterface $addedDate = null; + #[Groups(['project:read'])] + protected ?\DateTimeInterface $lastModified = null; + + /******************************************************************************** * * Getters From ec4eccdf0853a23c21f2ea5713ad8e61fb0e5755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Wed, 4 Oct 2023 00:50:28 +0200 Subject: [PATCH 62/62] Removed unnecessary commented code --- ...cumentedAPIPropertiesJSONSchemaFactory.php | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/src/ApiPlatform/AddDocumentedAPIPropertiesJSONSchemaFactory.php b/src/ApiPlatform/AddDocumentedAPIPropertiesJSONSchemaFactory.php index c13af396..4b5db003 100644 --- a/src/ApiPlatform/AddDocumentedAPIPropertiesJSONSchemaFactory.php +++ b/src/ApiPlatform/AddDocumentedAPIPropertiesJSONSchemaFactory.php @@ -65,31 +65,6 @@ class AddDocumentedAPIPropertiesJSONSchemaFactory implements SchemaFactoryInterf $api_property, $serializerContext ?? [], $format); } - /*if ($className === Attachment::class) { - $api_property = new ApiProperty(description: 'Test'); - $this->buildPropertySchema($schema, 'Attachment-Read', 'media_url', $api_property, $serializerContext ?? [], - $format); - }*/ - - //Add media_url and thumbnail_url to the Attachment schema - /*if ($className === Attachment::class) { - $tmp = $schema->getDefinitions()->getArrayCopy(); - $tmp['properties']['media_url'] = [ - 'type' => 'string', - 'readOnly' => true, - 'format' => 'uri', - 'description' => 'The URL to the attachment', - ]; - $tmp['properties']['thumbnail_url'] = [ - 'type' => 'string', - 'readOnly' => true, - 'format' => 'uri', - 'description' => 'The URL to the thumbnail of the attachment', - ]; - $schema->setDefinitions(new \ArrayObject($tmp)); - }*/ - - //Fd return $schema; }