diff --git a/.docker/partdb-entrypoint.sh b/.docker/partdb-entrypoint.sh index 3e06256a..5bfc6b48 100644 --- a/.docker/partdb-entrypoint.sh +++ b/.docker/partdb-entrypoint.sh @@ -39,8 +39,8 @@ if [ -d /var/www/html/var/db ]; then fi fi -# Start PHP-FPM -service php8.1-fpm start +# Start PHP-FPM (the PHP_VERSION is replaced by the configured version in the Dockerfile) +service phpPHP_VERSION-fpm start # first arg is `-f` or `--some-option` (taken from https://github.com/docker-library/php/blob/master/8.2/bullseye/apache/docker-php-entrypoint) if [ "${1#-}" != "$1" ]; then diff --git a/.docker/symfony.conf b/.docker/symfony.conf index 66893aee..5788a630 100644 --- a/.docker/symfony.conf +++ b/.docker/symfony.conf @@ -43,6 +43,7 @@ PassEnv PROVIDER_OCTOPART_CLIENT_ID PROVIDER_OCTOPART_SECRET PROVIDER_OCTOPART_CURRENCY PROVIDER_OCTOPART_COUNTRY PROVIDER_OCTOPART_SEARCH_LIMIT PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS PassEnv PROVIDER_MOUSER_KEY PROVIDER_MOUSER_SEARCH_OPTION PROVIDER_MOUSER_SEARCH_LIMIT PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE PassEnv PROVIDER_LCSC_ENABLED PROVIDER_LCSC_CURRENCY + PassEnv PROVIDER_OEMSECRETS_KEY PROVIDER_OEMSECRETS_COUNTRY_CODE PROVIDER_OEMSECRETS_CURRENCY PROVIDER_OEMSECRETS_ZERO_PRICE PROVIDER_OEMSECRETS_SET_PARAM PROVIDER_OEMSECRETS_SORT_CRITERIA PassEnv EDA_KICAD_CATEGORY_DEPTH # For most configuration files from conf-available/, which are diff --git a/.env b/.env index cf39b10d..ce2a6b03 100644 --- a/.env +++ b/.env @@ -181,6 +181,40 @@ PROVIDER_LCSC_ENABLED=0 # The currency to get prices in (e.g. EUR, USD, etc.) PROVIDER_LCSC_CURRENCY=EUR +# Oemsecrets Provider API 3.0.1: +# You can get your API key from https://www.oemsecrets.com/api +PROVIDER_OEMSECRETS_KEY= +# The country you want the output for +PROVIDER_OEMSECRETS_COUNTRY_CODE=DE +# Available country code are: +# AD, AE, AQ, AR, AT, AU, BE, BO, BR, BV, BY, CA, CH, CL, CN, CO, CZ, DE, DK, EC, EE, EH, +# ES, FI, FK, FO, FR, GB, GE, GF, GG, GI, GL, GR, GS, GY, HK, HM, HR, HU, IE, IM, IN, IS, +# IT, JM, JP, KP, KR, KZ, LI, LK, LT, LU, MC, MD, ME, MK, MT, NL, NO, NZ, PE, PH, PL, PT, +# PY, RO, RS, RU, SB, SD, SE, SG, SI, SJ, SK, SM, SO, SR, SY, SZ, TC, TF, TG, TH, TJ, TK, +# TM, TN, TO, TR, TT, TV, TW, TZ, UA, UG, UM, US, UY, UZ, VA, VE, VG, VI, VN, VU, WF, YE, +# ZA, ZM, ZW +# +# The currency you want the prices to be displayed in +PROVIDER_OEMSECRETS_CURRENCY=EUR +# Available currency are:AUD, CAD, CHF, CNY, DKK, EUR, GBP, HKD, ILS, INR, JPY, KRW, NOK, +# NZD, RUB, SEK, SGD, TWD, USD +# +# If PROVIDER_OEMSECRETS_ZERO_PRICE is set to 0, distributors with zero prices +# will be discarded from the creation of a new part (set to 1 otherwise) +PROVIDER_OEMSECRETS_ZERO_PRICE=0 +# +# When PROVIDER_OEMSECRETS_SET_PARAM is set to 1 the parameters for the part are generated +# from the description transforming unstructured descriptions into structured parameters; +# each parameter in description should have the form: "...;name1:value1;name2:value2" +PROVIDER_OEMSECRETS_SET_PARAM=1 +# +# This environment variable determines the sorting criteria for product results. +# The sorting process first arranges items based on the provided keyword. +# Then, if set to 'C', it further sorts by completeness (prioritizing items with the most +# detailed information). If set to 'M', it further sorts by manufacturer name. +#If unset or set to any other value, no sorting is performed. +PROVIDER_OEMSECRETS_SORT_CRITERIA=C + ################################################################################## # EDA integration related settings ################################################################################## diff --git a/Dockerfile b/Dockerfile index 14d5576e..15049166 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,22 +1,64 @@ -FROM debian:bullseye-slim +ARG BASE_IMAGE=debian:bookworm-slim +ARG PHP_VERSION=8.2 + +FROM ${BASE_IMAGE} AS base +ARG PHP_VERSION # Install needed dependencies for PHP build #RUN apt-get update && apt-get install -y pkg-config curl libcurl4-openssl-dev libicu-dev \ # libpng-dev libjpeg-dev libfreetype6-dev gnupg zip libzip-dev libjpeg62-turbo-dev libonig-dev libxslt-dev libwebp-dev vim \ # && apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/* -RUN apt-get update && apt-get -y install apt-transport-https lsb-release ca-certificates curl zip mariadb-client \ +RUN apt-get update && apt-get -y install \ + apt-transport-https \ + lsb-release \ + ca-certificates \ + curl \ + zip \ + mariadb-client \ + postgresql-client \ && curl -sSLo /usr/share/keyrings/deb.sury.org-php.gpg https://packages.sury.org/php/apt.gpg \ && sh -c 'echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list' \ && apt-get update && apt-get upgrade -y \ - && apt-get install -y apache2 php8.1 php8.1-fpm php8.1-opcache php8.1-curl php8.1-gd php8.1-mbstring php8.1-xml php8.1-bcmath php8.1-intl php8.1-zip php8.1-xsl php8.1-sqlite3 php8.1-mysql gpg sudo \ - && apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*; - -ENV APACHE_CONFDIR /etc/apache2 -ENV APACHE_ENVVARS $APACHE_CONFDIR/envvars - + && apt-get install -y \ + apache2 \ + php${PHP_VERSION} \ + php${PHP_VERSION}-fpm \ + php${PHP_VERSION}-opcache \ + php${PHP_VERSION}-curl \ + php${PHP_VERSION}-gd \ + php${PHP_VERSION}-mbstring \ + php${PHP_VERSION}-xml \ + php${PHP_VERSION}-bcmath \ + php${PHP_VERSION}-intl \ + php${PHP_VERSION}-zip \ + php${PHP_VERSION}-xsl \ + php${PHP_VERSION}-sqlite3 \ + php${PHP_VERSION}-mysql \ + php${PHP_VERSION}-pgsql \ + gpg \ + sudo \ + && apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/* \ # Create workdir and set permissions if directory does not exists -RUN mkdir -p /var/www/html && chown -R www-data:www-data /var/www/html + && mkdir -p /var/www/html \ + && chown -R www-data:www-data /var/www/html \ +# delete the "index.html" that installing Apache drops in here + && rm -rvf /var/www/html/* + +# Install node and yarn +RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ + echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ + curl -sL https://deb.nodesource.com/setup_20.x | bash - && \ + apt-get update && apt-get install -y \ + nodejs \ + yarn \ + && apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/* + +# Install composer +COPY --from=composer:latest /usr/bin/composer /usr/bin/composer + +ENV APACHE_CONFDIR=/etc/apache2 +ENV APACHE_ENVVARS=$APACHE_CONFDIR/envvars # Configure apache 2 (taken from https://github.com/docker-library/php/blob/master/8.2/bullseye/apache/Dockerfile) # generically convert lines like @@ -27,8 +69,6 @@ RUN mkdir -p /var/www/html && chown -R www-data:www-data /var/www/html # so that they can be overridden at runtime ("-e APACHE_RUN_USER=...") RUN sed -ri 's/^export ([^=]+)=(.*)$/: ${\1:=\2}\nexport \1/' "$APACHE_ENVVARS"; \ set -eux; . "$APACHE_ENVVARS"; \ - # delete the "index.html" that installing Apache drops in here - rm -rvf /var/www/html/*; \ \ # logs should go to stdout / stderr ln -sfT /dev/stderr "$APACHE_LOG_DIR/error.log"; \ @@ -36,82 +76,86 @@ RUN sed -ri 's/^export ([^=]+)=(.*)$/: ${\1:=\2}\nexport \1/' "$APACHE_ENVVARS" ln -sfT /dev/stdout "$APACHE_LOG_DIR/other_vhosts_access.log"; \ chown -R --no-dereference "$APACHE_RUN_USER:$APACHE_RUN_GROUP" "$APACHE_LOG_DIR"; -# Enable php-fpm -RUN a2enmod proxy_fcgi setenvif && a2enconf php8.1-fpm +# --- +FROM scratch AS apache-config +ARG PHP_VERSION # Configure php-fpm to log to stdout of the container (stdout of PID 1) # We have to use /proc/1/fd/1 because /dev/stdout or /proc/self/fd/1 does not point to the container stdout (because we use apache as entrypoint) # We also disable the clear_env option to allow the use of environment variables in php-fpm -RUN { \ - echo '[global]'; \ - echo 'error_log = /proc/1/fd/1'; \ - echo; \ - echo '[www]'; \ - echo 'access.log = /proc/1/fd/1'; \ - echo 'catch_workers_output = yes'; \ - echo 'decorate_workers_output = no'; \ - echo 'clear_env = no'; \ - } | tee "/etc/php/8.1/fpm/pool.d/zz-docker.conf" +COPY <'; \ - echo '\tSetHandler application/x-httpd-php'; \ - echo ''; \ - echo; \ - echo 'DirectoryIndex disabled'; \ - echo 'DirectoryIndex index.php index.html'; \ - echo; \ - echo ''; \ - echo '\tOptions -Indexes'; \ - echo '\tAllowOverride All'; \ - echo ''; \ - } | tee "$APACHE_CONFDIR/conf-available/docker-php.conf" \ - && a2enconf docker-php +COPY < + SetHandler application/x-httpd-php + + +DirectoryIndex disabled +DirectoryIndex index.php index.html + + + Options -Indexes + AllowOverride All + +EOF # Enable opcache and configure it recommended for symfony (see https://symfony.com/doc/current/performance.html) -RUN \ - { \ - echo 'opcache.memory_consumption=256'; \ - echo 'opcache.max_accelerated_files=20000'; \ - echo 'opcache.validate_timestamp=0'; \ - # Configure Realpath cache for performance - echo 'realpath_cache_size=4096K'; \ - echo 'realpath_cache_ttl=600'; \ - } > /etc/php/8.1/fpm/conf.d/symfony-recommended.ini +COPY < /etc/php/8.1/fpm/conf.d/partdb.ini +COPY < { + this._autocomplete.setIsOpen(false); + }); + } + } } \ No newline at end of file diff --git a/assets/controllers/elements/structural_entity_select_controller.js b/assets/controllers/elements/structural_entity_select_controller.js index 5280c339..4735dd2a 100644 --- a/assets/controllers/elements/structural_entity_select_controller.js +++ b/assets/controllers/elements/structural_entity_select_controller.js @@ -24,7 +24,6 @@ import {Controller} from "@hotwired/stimulus"; import {trans, ENTITY_SELECT_GROUP_NEW_NOT_ADDED_TO_DB} from '../../translator.js' - export default class extends Controller { _tomSelect; @@ -58,7 +57,21 @@ export default class extends Controller { render: { item: this.renderItem.bind(this), option: this.renderOption.bind(this), - option_create: function(data, escape) { + option_create: (data, escape) => { + //If the input starts with "->", we prepend the current selected value, for easier extension of existing values + //This here handles the display part, while the createItem function handles the actual creation + if (data.input.startsWith("->")) { + //Get current selected value + const current = this._tomSelect.getItem(this._tomSelect.getValue()).textContent.replaceAll("→", "->").trim(); + //Prepend it to the input + if (current) { + data.input = current + " " + data.input; + } else { + //If there is no current value, we remove the "->" + data.input = data.input.substring(2); + } + } + return '
 ' + escape(data.input) + '… ' + '(' + addHint +')' + '
'; @@ -76,6 +89,22 @@ export default class extends Controller { } createItem(input, callback) { + + //If the input starts with "->", we prepend the current selected value, for easier extension of existing values + if (input.startsWith("->")) { + //Get current selected value + let current = this._tomSelect.getItem(this._tomSelect.getValue()).textContent.replaceAll("→", "->").trim(); + //Replace no break spaces with normal spaces + current = current.replaceAll("\u00A0", " "); + //Prepend it to the input + if (current) { + input = current + " " + input; + } else { + //If there is no current value, we remove the "->" + input = input.substring(2); + } + } + callback({ //$%$ is a special value prefix, that is used to identify items, that are not yet in the DB value: '$%$' + input, diff --git a/assets/css/app/images.css b/assets/css/app/images.css index 9168484d..214776e7 100644 --- a/assets/css/app/images.css +++ b/assets/css/app/images.css @@ -51,7 +51,6 @@ .part-table-image { max-height: 40px; object-fit: contain; - width: 100%; } .part-info-image { @@ -61,4 +60,4 @@ .object-fit-cover { object-fit: cover; -} \ No newline at end of file +} diff --git a/assets/css/app/layout.css b/assets/css/app/layout.css index e00c823c..4be123a7 100644 --- a/assets/css/app/layout.css +++ b/assets/css/app/layout.css @@ -108,8 +108,8 @@ body { .back-to-top { cursor: pointer; position: fixed; - bottom: 20px; - right: 20px; + bottom: 60px; + right: 40px; display:none; z-index: 1030; } diff --git a/assets/css/app/tables.css b/assets/css/app/tables.css index 63f7860d..ae892f50 100644 --- a/assets/css/app/tables.css +++ b/assets/css/app/tables.css @@ -63,10 +63,6 @@ table.dataTable > tbody > tr.selected > td > a { margin-block-end: 0; } -.card-footer-table { - padding-top: 0; -} - table.dataTable { margin-top: 0 !important; } diff --git a/assets/js/register_events.js b/assets/js/register_events.js index 5d2aece0..9732c0c1 100644 --- a/assets/js/register_events.js +++ b/assets/js/register_events.js @@ -21,6 +21,7 @@ import {Dropdown} from "bootstrap"; import ClipboardJS from "clipboard"; +import {Modal} from "bootstrap"; class RegisterEventHelper { constructor() { @@ -31,9 +32,11 @@ class RegisterEventHelper { //Initialize ClipboardJS this.registerLoadHandler(() => { new ClipboardJS('.btn'); - }) + }); this.registerModalDropRemovalOnFormSubmit(); + + } registerModalDropRemovalOnFormSubmit() { @@ -43,6 +46,15 @@ class RegisterEventHelper { if (back_drop) { back_drop.remove(); } + + //Remove scroll-lock if it is still active + if (document.body.classList.contains('modal-open')) { + document.body.classList.remove('modal-open'); + + //Remove the padding-right and overflow:hidden from the body + document.body.style.paddingRight = ''; + document.body.style.overflow = ''; + } }); } diff --git a/assets/tomselect/extend_existing_selection/extend_existing_selection.js b/assets/tomselect/extend_existing_selection/extend_existing_selection.js new file mode 100644 index 00000000..e69de29b diff --git a/composer.json b/composer.json index 0ce8e5e4..0659f2ee 100644 --- a/composer.json +++ b/composer.json @@ -41,8 +41,8 @@ "nyholm/psr7": "^1.1", "ocramius/proxy-manager": "2.2.*", "omines/datatables-bundle": "^0.8.0", + "paragonie/sodium_compat": "^1.21", "part-db/label-fonts": "^1.0", - "php-translation/symfony-bundle": "^0.14.0", "phpdocumentor/reflection-docblock": "^5.2", "phpstan/phpdoc-parser": "^1.23", "runtime/frankenphp-symfony": "^0.2.0", @@ -98,13 +98,14 @@ "dama/doctrine-test-bundle": "^v8.0.0", "doctrine/doctrine-fixtures-bundle": "^3.2", "ekino/phpstan-banned-code": "^v1.0.0", + "jbtronics/translation-editor-bundle": "^1.0", "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^1.4.7", "phpstan/phpstan-doctrine": "^1.2.11", "phpstan/phpstan-strict-rules": "^1.5", "phpstan/phpstan-symfony": "^1.1.7", "phpunit/phpunit": "^9.5", - "rector/rector": "^0.18.0", + "rector/rector": "^1.1.1", "roave/security-advisories": "dev-latest", "symfony/browser-kit": "6.4.*", "symfony/css-selector": "6.4.*", diff --git a/composer.lock b/composer.lock index 144ff0b0..779dc13a 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": "b87b2ee767fbe00edcad0eba6e7fb6fd", + "content-hash": "465fa2ab7f9f8dad2b8cfbeaa640c136", "packages": [ { "name": "amphp/amp", @@ -612,16 +612,16 @@ }, { "name": "amphp/pipeline", - "version": "v1.2.0", + "version": "v1.2.1", "source": { "type": "git", "url": "https://github.com/amphp/pipeline.git", - "reference": "f1c2ce35d27ae86ead018adb803eccca7421dd9b" + "reference": "66c095673aa5b6e689e63b52d19e577459129ab3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/pipeline/zipball/f1c2ce35d27ae86ead018adb803eccca7421dd9b", - "reference": "f1c2ce35d27ae86ead018adb803eccca7421dd9b", + "url": "https://api.github.com/repos/amphp/pipeline/zipball/66c095673aa5b6e689e63b52d19e577459129ab3", + "reference": "66c095673aa5b6e689e63b52d19e577459129ab3", "shasum": "" }, "require": { @@ -667,7 +667,7 @@ ], "support": { "issues": "https://github.com/amphp/pipeline/issues", - "source": "https://github.com/amphp/pipeline/tree/v1.2.0" + "source": "https://github.com/amphp/pipeline/tree/v1.2.1" }, "funding": [ { @@ -675,7 +675,7 @@ "type": "github" } ], - "time": "2024-03-10T14:48:16+00:00" + "time": "2024-07-04T00:56:47+00:00" }, { "name": "amphp/process", @@ -889,16 +889,16 @@ }, { "name": "amphp/sync", - "version": "v2.2.0", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/amphp/sync.git", - "reference": "375ef5b54a0d12c38e12728dde05a55e30f2fbec" + "reference": "217097b785130d77cfcc58ff583cf26cd1770bf1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/sync/zipball/375ef5b54a0d12c38e12728dde05a55e30f2fbec", - "reference": "375ef5b54a0d12c38e12728dde05a55e30f2fbec", + "url": "https://api.github.com/repos/amphp/sync/zipball/217097b785130d77cfcc58ff583cf26cd1770bf1", + "reference": "217097b785130d77cfcc58ff583cf26cd1770bf1", "shasum": "" }, "require": { @@ -952,7 +952,7 @@ ], "support": { "issues": "https://github.com/amphp/sync/issues", - "source": "https://github.com/amphp/sync/tree/v2.2.0" + "source": "https://github.com/amphp/sync/tree/v2.3.0" }, "funding": [ { @@ -960,7 +960,7 @@ "type": "github" } ], - "time": "2024-03-12T01:00:01+00:00" + "time": "2024-08-03T19:31:26+00:00" }, { "name": "amphp/windows-registry", @@ -1016,16 +1016,16 @@ }, { "name": "api-platform/core", - "version": "v3.3.6", + "version": "v3.4.5", "source": { "type": "git", "url": "https://github.com/api-platform/core.git", - "reference": "c7f25dc6c7ca82ade7f8a0e5d63393b25b4a2db5" + "reference": "8bdf04409a4e873c889d22c4e05a3a81137f2a57" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/core/zipball/c7f25dc6c7ca82ade7f8a0e5d63393b25b4a2db5", - "reference": "c7f25dc6c7ca82ade7f8a0e5d63393b25b4a2db5", + "url": "https://api.github.com/repos/api-platform/core/zipball/8bdf04409a4e873c889d22c4e05a3a81137f2a57", + "reference": "8bdf04409a4e873c889d22c4e05a3a81137f2a57", "shasum": "" }, "require": { @@ -1034,13 +1034,13 @@ "psr/cache": "^1.0 || ^2.0 || ^3.0", "psr/container": "^1.0 || ^2.0", "symfony/deprecation-contracts": "^3.1", - "symfony/http-foundation": "^6.4 || ^7.0", - "symfony/http-kernel": "^6.4 || ^7.0", - "symfony/property-access": "^6.4 || ^7.0", - "symfony/property-info": "^6.4 || ^7.0", - "symfony/serializer": "^6.4 || ^7.0", + "symfony/http-foundation": "^6.4 || ^7.1", + "symfony/http-kernel": "^6.4 || ^7.1", + "symfony/property-access": "^6.4 || ^7.1", + "symfony/property-info": "^6.4 || ^7.1", + "symfony/serializer": "^6.4 || ^7.1", "symfony/translation-contracts": "^3.3", - "symfony/web-link": "^6.4 || ^7.0", + "symfony/web-link": "^6.4 || ^7.1", "willdurand/negotiation": "^3.0" }, "conflict": { @@ -1052,15 +1052,56 @@ "elasticsearch/elasticsearch": ">=8.0,<8.4", "phpspec/prophecy": "<1.15", "phpunit/phpunit": "<9.5", - "symfony/framework-bundle": "6.4.6 || 7.0.6", + "symfony/framework-bundle": "6.4.6 || 7.1.6", "symfony/var-exporter": "<6.1.1" }, + "replace": { + "api-platform/doctrine-common": "self.version", + "api-platform/doctrine-odm": "self.version", + "api-platform/doctrine-orm": "self.version", + "api-platform/documentation": "self.version", + "api-platform/elasticsearch": "self.version", + "api-platform/graphql": "self.version", + "api-platform/http-cache": "self.version", + "api-platform/hydra": "self.version", + "api-platform/json-api": "self.version", + "api-platform/json-hal": "self.version", + "api-platform/json-schema": "self.version", + "api-platform/jsonld": "self.version", + "api-platform/laravel": "self.version", + "api-platform/metadata": "self.version", + "api-platform/openapi": "self.version", + "api-platform/parameter-validator": "self.version", + "api-platform/ramsey-uuid": "self.version", + "api-platform/serializer": "self.version", + "api-platform/state": "self.version", + "api-platform/symfony": "self.version", + "api-platform/validator": "self.version" + }, "require-dev": { + "api-platform/doctrine-common": "^3.4 || ^4.0", + "api-platform/doctrine-odm": "^3.4 || ^4.0", + "api-platform/doctrine-orm": "^3.4 || ^4.0", + "api-platform/documentation": "^3.4 || ^4.0", + "api-platform/elasticsearch": "^3.4 || ^4.0", + "api-platform/graphql": "^3.4 || ^4.0", + "api-platform/http-cache": "^3.4 || ^4.0", + "api-platform/hydra": "^3.4 || ^4.0", + "api-platform/json-api": "^3.3 || ^4.0", + "api-platform/json-schema": "^3.4 || ^4.0", + "api-platform/jsonld": "^3.4 || ^4.0", + "api-platform/metadata": "^3.4 || ^4.0", + "api-platform/openapi": "^3.4 || ^4.0", + "api-platform/parameter-validator": "^3.4", + "api-platform/ramsey-uuid": "^3.4 || ^4.0", + "api-platform/serializer": "^3.4 || ^4.0", + "api-platform/state": "^3.4 || ^4.0", + "api-platform/validator": "^3.4 || ^4.0", "behat/behat": "^3.11", "behat/mink": "^1.9", "doctrine/cache": "^1.11 || ^2.1", "doctrine/common": "^3.2.2", - "doctrine/dbal": "^3.4.0", + "doctrine/dbal": "^3.4.0 || ^4.0", "doctrine/doctrine-bundle": "^1.12 || ^2.0", "doctrine/mongodb-odm": "^2.2", "doctrine/mongodb-odm-bundle": "^4.0 || ^5.0", @@ -1069,7 +1110,7 @@ "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", + "guzzlehttp/guzzle": "^6.0 || ^7.1", "jangregor/phpstan-prophecy": "^1.0", "justinrainbow/json-schema": "^5.2.1", "phpspec/prophecy-phpunit": "^2.0", @@ -1082,41 +1123,42 @@ "phpunit/phpunit": "^9.6", "psr/log": "^1.0 || ^2.0 || ^3.0", "ramsey/uuid": "^3.9.7 || ^4.0", - "ramsey/uuid-doctrine": "^1.4 || ^2.0", + "ramsey/uuid-doctrine": "^1.4 || ^2.0 || ^3.0", "sebastian/comparator": "<5.0", "soyuka/contexts": "v3.3.9", - "soyuka/pmu": "^0.0.2", + "soyuka/pmu": "^0.0.12", "soyuka/stubs-mongodb": "^1.0", - "symfony/asset": "^6.4 || ^7.0", - "symfony/browser-kit": "^6.4 || ^7.0", - "symfony/cache": "^6.4 || ^7.0", - "symfony/config": "^6.4 || ^7.0", - "symfony/console": "^6.4 || ^7.0", - "symfony/css-selector": "^6.4 || ^7.0", - "symfony/dependency-injection": "^6.4 || ^7.0.12", - "symfony/doctrine-bridge": "^6.4 || ^7.0", - "symfony/dom-crawler": "^6.4 || ^7.0", - "symfony/error-handler": "^6.4 || ^7.0", - "symfony/event-dispatcher": "^6.4 || ^7.0", - "symfony/expression-language": "^6.4 || ^7.0", - "symfony/finder": "^6.4 || ^7.0", - "symfony/form": "^6.4 || ^7.0", - "symfony/framework-bundle": "^6.4 || ^7.0", - "symfony/http-client": "^6.4 || ^7.0", - "symfony/intl": "^6.4 || ^7.0", + "symfony/asset": "^6.4 || ^7.1", + "symfony/browser-kit": "^6.4 || ^7.1", + "symfony/cache": "^6.4 || ^7.1", + "symfony/config": "^6.4 || ^7.1", + "symfony/console": "^6.4 || ^7.1", + "symfony/css-selector": "^6.4 || ^7.1", + "symfony/dependency-injection": "^6.4 || ^7.1", + "symfony/doctrine-bridge": "^6.4 || ^7.1", + "symfony/dom-crawler": "^6.4 || ^7.1", + "symfony/error-handler": "^6.4 || ^7.1", + "symfony/event-dispatcher": "^6.4 || ^7.1", + "symfony/expression-language": "^6.4 || ^7.1", + "symfony/finder": "^6.4 || ^7.1", + "symfony/form": "^6.4 || ^7.1", + "symfony/framework-bundle": "^6.4 || ^7.1", + "symfony/http-client": "^6.4 || ^7.1", + "symfony/intl": "^6.4 || ^7.1", "symfony/maker-bundle": "^1.24", "symfony/mercure-bundle": "*", - "symfony/messenger": "^6.4 || ^7.0", - "symfony/phpunit-bridge": "^6.4.1 || ^7.0", - "symfony/routing": "^6.4 || ^7.0", - "symfony/security-bundle": "^6.4 || ^7.0", - "symfony/security-core": "^6.4 || ^7.0", - "symfony/stopwatch": "^6.4 || ^7.0", - "symfony/twig-bundle": "^6.4 || ^7.0", - "symfony/uid": "^6.4 || ^7.0", - "symfony/validator": "^6.4 || ^7.0", - "symfony/web-profiler-bundle": "^6.4 || ^7.0", - "symfony/yaml": "^6.4 || ^7.0", + "symfony/messenger": "^6.4 || ^7.1", + "symfony/phpunit-bridge": "^6.4.1 || ^7.1", + "symfony/routing": "^6.4 || ^7.1", + "symfony/security-bundle": "^6.4 || ^7.1", + "symfony/security-core": "^6.4 || ^7.1", + "symfony/stopwatch": "^6.4 || ^7.1", + "symfony/string": "^6.4 || ^7.1", + "symfony/twig-bundle": "^6.4 || ^7.1", + "symfony/uid": "^6.4 || ^7.1", + "symfony/validator": "^6.4 || ^7.1", + "symfony/web-profiler-bundle": "^6.4 || ^7.1", + "symfony/yaml": "^6.4 || ^7.1", "twig/twig": "^1.42.3 || ^2.12 || ^3.0", "webonyx/graphql-php": "^14.0 || ^15.0" }, @@ -1141,31 +1183,22 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.3.x-dev" + "dev-3.4": "3.4.x-dev", + "dev-main": "4.0.x-dev" }, "symfony": { - "require": "^6.4 || ^7.0" + "require": "^6.4 || ^7.1" }, - "projects": [ - "api-platform/doctrine-common", - "api-platform/doctrine-orm", - "api-platform/doctrine-odm", - "api-platform/metadata", - "api-platform/json-schema", - "api-platform/elasticsearch", - "api-platform/jsonld", - "api-platform/hydra", - "api-platform/openapi", - "api-platform/graphql", - "api-platform/http-cache", - "api-platform/documentation", - "api-platform/parameter-validator", - "api-platform/ramsey-uuid", - "api-platform/serializer", - "api-platform/state", - "api-platform/symfony", - "api-platform/validator" - ] + "pmu": { + "projects": [ + "./src/*/composer.json", + "src/Doctrine/*/composer.json" + ] + }, + "thanks": { + "name": "api-platform/api-platform", + "url": "https://github.com/api-platform/api-platform" + } }, "autoload": { "psr-4": { @@ -1198,22 +1231,22 @@ ], "support": { "issues": "https://github.com/api-platform/core/issues", - "source": "https://github.com/api-platform/core/tree/v3.3.6" + "source": "https://github.com/api-platform/core/tree/v3.4.5" }, - "time": "2024-06-14T07:14:21+00:00" + "time": "2024-10-26T07:20:01+00:00" }, { "name": "beberlei/assert", - "version": "v3.3.2", + "version": "v3.3.3", "source": { "type": "git", "url": "https://github.com/beberlei/assert.git", - "reference": "cb70015c04be1baee6f5f5c953703347c0ac1655" + "reference": "b5fd8eacd8915a1b627b8bfc027803f1939734dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/beberlei/assert/zipball/cb70015c04be1baee6f5f5c953703347c0ac1655", - "reference": "cb70015c04be1baee6f5f5c953703347c0ac1655", + "url": "https://api.github.com/repos/beberlei/assert/zipball/b5fd8eacd8915a1b627b8bfc027803f1939734dd", + "reference": "b5fd8eacd8915a1b627b8bfc027803f1939734dd", "shasum": "" }, "require": { @@ -1221,7 +1254,7 @@ "ext-json": "*", "ext-mbstring": "*", "ext-simplexml": "*", - "php": "^7.0 || ^8.0" + "php": "^7.1 || ^8.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "*", @@ -1265,9 +1298,9 @@ ], "support": { "issues": "https://github.com/beberlei/assert/issues", - "source": "https://github.com/beberlei/assert/tree/v3.3.2" + "source": "https://github.com/beberlei/assert/tree/v3.3.3" }, - "time": "2021-12-16T21:41:27+00:00" + "time": "2024-07-15T13:18:35+00:00" }, { "name": "beberlei/doctrineextensions", @@ -1393,16 +1426,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.5.0", + "version": "1.5.3", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "0c5ccfcfea312b5c5a190a21ac5cef93f74baf99" + "reference": "3b1fc3f0be055baa7c6258b1467849c3e8204eb2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/0c5ccfcfea312b5c5a190a21ac5cef93f74baf99", - "reference": "0c5ccfcfea312b5c5a190a21ac5cef93f74baf99", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/3b1fc3f0be055baa7c6258b1467849c3e8204eb2", + "reference": "3b1fc3f0be055baa7c6258b1467849c3e8204eb2", "shasum": "" }, "require": { @@ -1412,8 +1445,8 @@ }, "require-dev": { "phpstan/phpstan": "^1.10", - "psr/log": "^1.0", - "symfony/phpunit-bridge": "^4.2 || ^5", + "phpunit/phpunit": "^8 || ^9", + "psr/log": "^1.0 || ^2.0 || ^3.0", "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, "type": "library", @@ -1449,7 +1482,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.5.0" + "source": "https://github.com/composer/ca-bundle/tree/1.5.3" }, "funding": [ { @@ -1465,7 +1498,7 @@ "type": "tidelift" } ], - "time": "2024-03-15T14:00:32+00:00" + "time": "2024-11-04T10:15:26+00:00" }, { "name": "composer/package-versions-deprecated", @@ -1584,82 +1617,6 @@ }, "time": "2024-04-12T12:12:48+00:00" }, - { - "name": "doctrine/annotations", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", - "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", - "shasum": "" - }, - "require": { - "doctrine/lexer": "^2 || ^3", - "ext-tokenizer": "*", - "php": "^7.2 || ^8.0", - "psr/cache": "^1 || ^2 || ^3" - }, - "require-dev": { - "doctrine/cache": "^2.0", - "doctrine/coding-standard": "^10", - "phpstan/phpstan": "^1.8.0", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "symfony/cache": "^5.4 || ^6", - "vimeo/psalm": "^4.10" - }, - "suggest": { - "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Docblock Annotations Parser", - "homepage": "https://www.doctrine-project.org/projects/annotations.html", - "keywords": [ - "annotations", - "docblock", - "parser" - ], - "support": { - "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/2.0.1" - }, - "time": "2023-02-02T22:02:53+00:00" - }, { "name": "doctrine/cache", "version": "2.2.0", @@ -1841,22 +1798,23 @@ }, { "name": "doctrine/data-fixtures", - "version": "1.7.0", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/doctrine/data-fixtures.git", - "reference": "bbcb74f2ac6dbe81a14b3c3687d7623490a0448f" + "reference": "d2ff5046b263868baf6e9b06cf4918f60096c0d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/bbcb74f2ac6dbe81a14b3c3687d7623490a0448f", - "reference": "bbcb74f2ac6dbe81a14b3c3687d7623490a0448f", + "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/d2ff5046b263868baf6e9b06cf4918f60096c0d0", + "reference": "d2ff5046b263868baf6e9b06cf4918f60096c0d0", "shasum": "" }, "require": { "doctrine/deprecations": "^0.5.3 || ^1.0", - "doctrine/persistence": "^2.0|^3.0", - "php": "^7.4 || ^8.0" + "doctrine/persistence": "^2.0 || ^3.0", + "php": "^7.4 || ^8.0", + "symfony/polyfill-php80": "^1" }, "conflict": { "doctrine/dbal": "<3.5 || >=5", @@ -1870,11 +1828,12 @@ "doctrine/mongodb-odm": "^1.3.0 || ^2.0.0", "doctrine/orm": "^2.14 || ^3", "ext-sqlite3": "*", + "fig/log-test": "^1", "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^9.6.13 || ^10.4.2", + "psr/log": "^1.1 || ^2 || ^3", "symfony/cache": "^5.4 || ^6.3 || ^7", - "symfony/var-exporter": "^5.4 || ^6.3 || ^7", - "vimeo/psalm": "^5.9" + "symfony/var-exporter": "^5.4 || ^6.3 || ^7" }, "suggest": { "alcaeus/mongo-php-adapter": "For using MongoDB ODM 1.3 with PHP 7 (deprecated)", @@ -1905,7 +1864,7 @@ ], "support": { "issues": "https://github.com/doctrine/data-fixtures/issues", - "source": "https://github.com/doctrine/data-fixtures/tree/1.7.0" + "source": "https://github.com/doctrine/data-fixtures/tree/1.8.0" }, "funding": [ { @@ -1921,20 +1880,20 @@ "type": "tidelift" } ], - "time": "2023-11-24T11:18:31+00:00" + "time": "2024-11-04T22:36:12+00:00" }, { "name": "doctrine/dbal", - "version": "4.0.3", + "version": "4.2.1", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "8edbce73bc1aa2251ba8c754fc440f8e02c661bc" + "reference": "dadd35300837a3a2184bd47d403333b15d0a9bd0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/8edbce73bc1aa2251ba8c754fc440f8e02c661bc", - "reference": "8edbce73bc1aa2251ba8c754fc440f8e02c661bc", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/dadd35300837a3a2184bd47d403333b15d0a9bd0", + "reference": "dadd35300837a3a2184bd47d403333b15d0a9bd0", "shasum": "" }, "require": { @@ -1947,16 +1906,16 @@ "doctrine/coding-standard": "12.0.0", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2023.2", - "phpstan/phpstan": "1.11.1", + "phpstan/phpstan": "1.12.6", "phpstan/phpstan-phpunit": "1.4.0", "phpstan/phpstan-strict-rules": "^1.6", - "phpunit/phpunit": "10.5.20", + "phpunit/phpunit": "10.5.30", "psalm/plugin-phpunit": "0.19.0", "slevomat/coding-standard": "8.13.1", - "squizlabs/php_codesniffer": "3.9.2", + "squizlabs/php_codesniffer": "3.10.2", "symfony/cache": "^6.3.8|^7.0", "symfony/console": "^5.4|^6.3|^7.0", - "vimeo/psalm": "5.24.0" + "vimeo/psalm": "5.25.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -2013,7 +1972,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/4.0.3" + "source": "https://github.com/doctrine/dbal/tree/4.2.1" }, "funding": [ { @@ -2029,7 +1988,7 @@ "type": "tidelift" } ], - "time": "2024-06-12T06:58:42+00:00" + "time": "2024-10-10T18:01:27+00:00" }, { "name": "doctrine/deprecations", @@ -2080,16 +2039,16 @@ }, { "name": "doctrine/doctrine-bundle", - "version": "2.12.0", + "version": "2.13.0", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineBundle.git", - "reference": "5418e811a14724068e95e0ba43353b903ada530f" + "reference": "ca59d84b8e63143ce1aed90cdb333ba329d71563" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/5418e811a14724068e95e0ba43353b903ada530f", - "reference": "5418e811a14724068e95e0ba43353b903ada530f", + "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/ca59d84b8e63143ce1aed90cdb333ba329d71563", + "reference": "ca59d84b8e63143ce1aed90cdb333ba329d71563", "shasum": "" }, "require": { @@ -2180,7 +2139,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineBundle/issues", - "source": "https://github.com/doctrine/DoctrineBundle/tree/2.12.0" + "source": "https://github.com/doctrine/DoctrineBundle/tree/2.13.0" }, "funding": [ { @@ -2196,7 +2155,7 @@ "type": "tidelift" } ], - "time": "2024-03-19T07:20:37+00:00" + "time": "2024-09-01T09:46:40+00:00" }, { "name": "doctrine/doctrine-migrations-bundle", @@ -2621,21 +2580,21 @@ }, { "name": "doctrine/migrations", - "version": "3.7.4", + "version": "3.8.2", "source": { "type": "git", "url": "https://github.com/doctrine/migrations.git", - "reference": "954e0a314c2f0eb9fb418210445111747de254a6" + "reference": "5007eb1168691225ac305fe16856755c20860842" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/migrations/zipball/954e0a314c2f0eb9fb418210445111747de254a6", - "reference": "954e0a314c2f0eb9fb418210445111747de254a6", + "url": "https://api.github.com/repos/doctrine/migrations/zipball/5007eb1168691225ac305fe16856755c20860842", + "reference": "5007eb1168691225ac305fe16856755c20860842", "shasum": "" }, "require": { "composer-runtime-api": "^2", - "doctrine/dbal": "^3.5.1 || ^4", + "doctrine/dbal": "^3.6 || ^4", "doctrine/deprecations": "^0.5.3 || ^1", "doctrine/event-manager": "^1.2 || ^2.0", "php": "^8.1", @@ -2653,6 +2612,7 @@ "doctrine/persistence": "^2 || ^3", "doctrine/sql-formatter": "^1.0", "ext-pdo_sqlite": "*", + "fig/log-test": "^1", "phpstan/phpstan": "^1.10", "phpstan/phpstan-deprecation-rules": "^1.1", "phpstan/phpstan-phpunit": "^1.3", @@ -2673,7 +2633,7 @@ "type": "library", "autoload": { "psr-4": { - "Doctrine\\Migrations\\": "lib/Doctrine/Migrations" + "Doctrine\\Migrations\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -2703,7 +2663,7 @@ ], "support": { "issues": "https://github.com/doctrine/migrations/issues", - "source": "https://github.com/doctrine/migrations/tree/3.7.4" + "source": "https://github.com/doctrine/migrations/tree/3.8.2" }, "funding": [ { @@ -2719,20 +2679,20 @@ "type": "tidelift" } ], - "time": "2024-03-06T13:41:11+00:00" + "time": "2024-10-10T21:35:27+00:00" }, { "name": "doctrine/orm", - "version": "3.2.0", + "version": "3.3.0", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "37946d3a21ddf837c0d84f8156ee60a92102e332" + "reference": "69958152e661aa9c14e80d1ee4962863485aa60b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/37946d3a21ddf837c0d84f8156ee60a92102e332", - "reference": "37946d3a21ddf837c0d84f8156ee60a92102e332", + "url": "https://api.github.com/repos/doctrine/orm/zipball/69958152e661aa9c14e80d1ee4962863485aa60b", + "reference": "69958152e661aa9c14e80d1ee4962863485aa60b", "shasum": "" }, "require": { @@ -2754,7 +2714,10 @@ "require-dev": { "doctrine/coding-standard": "^12.0", "phpbench/phpbench": "^1.0", - "phpstan/phpstan": "1.11.1", + "phpdocumentor/guides-cli": "^1.4", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "1.12.6", + "phpstan/phpstan-deprecation-rules": "^1.2", "phpunit/phpunit": "^10.4.0", "psr/log": "^1 || ^2 || ^3", "squizlabs/php_codesniffer": "3.7.2", @@ -2805,22 +2768,22 @@ ], "support": { "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/3.2.0" + "source": "https://github.com/doctrine/orm/tree/3.3.0" }, - "time": "2024-05-23T14:27:52+00:00" + "time": "2024-10-12T20:07:18+00:00" }, { "name": "doctrine/persistence", - "version": "3.3.2", + "version": "3.4.0", "source": { "type": "git", "url": "https://github.com/doctrine/persistence.git", - "reference": "477da35bd0255e032826f440b94b3e37f2d56f42" + "reference": "0ea965320cec355dba75031c1b23d4c78362e3ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/persistence/zipball/477da35bd0255e032826f440b94b3e37f2d56f42", - "reference": "477da35bd0255e032826f440b94b3e37f2d56f42", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/0ea965320cec355dba75031c1b23d4c78362e3ff", + "reference": "0ea965320cec355dba75031c1b23d4c78362e3ff", "shasum": "" }, "require": { @@ -2832,15 +2795,13 @@ "doctrine/common": "<2.10" }, "require-dev": { - "composer/package-versions-deprecated": "^1.11", - "doctrine/coding-standard": "^11", + "doctrine/coding-standard": "^12", "doctrine/common": "^3.0", - "phpstan/phpstan": "1.9.4", + "phpstan/phpstan": "1.12.7", "phpstan/phpstan-phpunit": "^1", "phpstan/phpstan-strict-rules": "^1.1", - "phpunit/phpunit": "^8.5 || ^9.5", - "symfony/cache": "^4.4 || ^5.4 || ^6.0", - "vimeo/psalm": "4.30.0 || 5.3.0" + "phpunit/phpunit": "^8.5.38 || ^9.5", + "symfony/cache": "^4.4 || ^5.4 || ^6.0 || ^7.0" }, "type": "library", "autoload": { @@ -2889,7 +2850,7 @@ ], "support": { "issues": "https://github.com/doctrine/persistence/issues", - "source": "https://github.com/doctrine/persistence/tree/3.3.2" + "source": "https://github.com/doctrine/persistence/tree/3.4.0" }, "funding": [ { @@ -2905,20 +2866,20 @@ "type": "tidelift" } ], - "time": "2024-03-12T14:54:36+00:00" + "time": "2024-10-30T19:48:12+00:00" }, { "name": "doctrine/sql-formatter", - "version": "1.4.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/doctrine/sql-formatter.git", - "reference": "d1ac84aef745c69ea034929eb6d65a6908b675cc" + "reference": "b784cbde727cf806721451dde40eff4fec3bbe86" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/sql-formatter/zipball/d1ac84aef745c69ea034929eb6d65a6908b675cc", - "reference": "d1ac84aef745c69ea034929eb6d65a6908b675cc", + "url": "https://api.github.com/repos/doctrine/sql-formatter/zipball/b784cbde727cf806721451dde40eff4fec3bbe86", + "reference": "b784cbde727cf806721451dde40eff4fec3bbe86", "shasum": "" }, "require": { @@ -2926,6 +2887,7 @@ }, "require-dev": { "doctrine/coding-standard": "^12", + "ergebnis/phpunit-slow-test-detector": "^2.14", "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^10.5", "vimeo/psalm": "^5.24" @@ -2958,9 +2920,9 @@ ], "support": { "issues": "https://github.com/doctrine/sql-formatter/issues", - "source": "https://github.com/doctrine/sql-formatter/tree/1.4.0" + "source": "https://github.com/doctrine/sql-formatter/tree/1.5.1" }, - "time": "2024-05-08T08:12:09+00:00" + "time": "2024-10-21T18:21:57+00:00" }, { "name": "dompdf/dompdf", @@ -3372,12 +3334,12 @@ "source": { "type": "git", "url": "https://github.com/florianv/symfony-swap.git", - "reference": "ceb79705f17145367bb968e7b7dda87297c20645" + "reference": "c8cd268ad6e2f636f10b91df9850e3941d7f5807" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/florianv/symfony-swap/zipball/ceb79705f17145367bb968e7b7dda87297c20645", - "reference": "ceb79705f17145367bb968e7b7dda87297c20645", + "url": "https://api.github.com/repos/florianv/symfony-swap/zipball/c8cd268ad6e2f636f10b91df9850e3941d7f5807", + "reference": "c8cd268ad6e2f636f10b91df9850e3941d7f5807", "shasum": "" }, "require": { @@ -3433,7 +3395,7 @@ "issues": "https://github.com/florianv/symfony-swap/issues", "source": "https://github.com/florianv/symfony-swap/tree/master" }, - "time": "2024-04-11T09:21:44+00:00" + "time": "2024-07-09T13:51:01+00:00" }, { "name": "friendsofphp/proxy-manager-lts", @@ -3643,22 +3605,22 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.8.1", + "version": "7.9.2", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "41042bc7ab002487b876a0683fc8dce04ddce104" + "reference": "d281ed313b989f213357e3be1a179f02196ac99b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", - "reference": "41042bc7ab002487b876a0683fc8dce04ddce104", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0.1", - "guzzlehttp/psr7": "^1.9.1 || ^2.5.1", + "guzzlehttp/promises": "^1.5.3 || ^2.0.3", + "guzzlehttp/psr7": "^2.7.0", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -3669,9 +3631,9 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", - "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", + "guzzle/client-integration-tests": "3.0.2", "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.36 || ^9.6.15", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -3749,7 +3711,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.8.1" + "source": "https://github.com/guzzle/guzzle/tree/7.9.2" }, "funding": [ { @@ -3765,20 +3727,20 @@ "type": "tidelift" } ], - "time": "2023-12-03T20:35:24+00:00" + "time": "2024-07-24T11:22:20+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.0.2", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223" + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223", - "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223", + "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455", + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455", "shasum": "" }, "require": { @@ -3786,7 +3748,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.36 || ^9.6.15" + "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "type": "library", "extra": { @@ -3832,7 +3794,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.2" + "source": "https://github.com/guzzle/promises/tree/2.0.4" }, "funding": [ { @@ -3848,20 +3810,20 @@ "type": "tidelift" } ], - "time": "2023-12-03T20:19:20+00:00" + "time": "2024-10-17T10:06:22+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.6.2", + "version": "2.7.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", - "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201", "shasum": "" }, "require": { @@ -3876,8 +3838,8 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.36 || ^9.6.15" + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -3948,7 +3910,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.6.2" + "source": "https://github.com/guzzle/psr7/tree/2.7.0" }, "funding": [ { @@ -3964,7 +3926,7 @@ "type": "tidelift" } ], - "time": "2023-12-03T20:05:35+00:00" + "time": "2024-07-18T11:15:46+00:00" }, { "name": "hshn/base64-encoded-file", @@ -4092,16 +4054,16 @@ }, { "name": "jbtronics/2fa-webauthn", - "version": "v2.2.1", + "version": "v2.2.2", "source": { "type": "git", "url": "https://github.com/jbtronics/2fa-webauthn.git", - "reference": "eb62e3f46321f6050bbe100631ae0777216f36ca" + "reference": "4341335ab9667f7347a24d60c29d143f25371e82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jbtronics/2fa-webauthn/zipball/eb62e3f46321f6050bbe100631ae0777216f36ca", - "reference": "eb62e3f46321f6050bbe100631ae0777216f36ca", + "url": "https://api.github.com/repos/jbtronics/2fa-webauthn/zipball/4341335ab9667f7347a24d60c29d143f25371e82", + "reference": "4341335ab9667f7347a24d60c29d143f25371e82", "shasum": "" }, "require": { @@ -4111,7 +4073,7 @@ "psr/log": "^3.0.0|^2.0.0", "scheb/2fa-bundle": "^6.0.0|^7.0.0", "symfony/framework-bundle": "^6.0|^7.0", - "symfony/psr-http-message-bridge": "^2.1", + "symfony/psr-http-message-bridge": "^2.1|^6.1|^7.0", "symfony/uid": "^6.0|^7.0", "web-auth/webauthn-lib": "^4.7" }, @@ -4146,9 +4108,9 @@ ], "support": { "issues": "https://github.com/jbtronics/2fa-webauthn/issues", - "source": "https://github.com/jbtronics/2fa-webauthn/tree/v2.2.1" + "source": "https://github.com/jbtronics/2fa-webauthn/tree/v2.2.2" }, - "time": "2024-01-15T15:38:49+00:00" + "time": "2024-07-21T19:24:26+00:00" }, { "name": "jbtronics/dompdf-font-loader-bundle", @@ -4502,32 +4464,31 @@ }, { "name": "knpuniversity/oauth2-client-bundle", - "version": "v2.18.1", + "version": "v2.18.3", "source": { "type": "git", "url": "https://github.com/knpuniversity/oauth2-client-bundle.git", - "reference": "1d59f49f164805b45f95f92cf743781bc2ba7d2b" + "reference": "c38ca88a70aae3694ca346a41b13b9a8f6e33ed4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/knpuniversity/oauth2-client-bundle/zipball/1d59f49f164805b45f95f92cf743781bc2ba7d2b", - "reference": "1d59f49f164805b45f95f92cf743781bc2ba7d2b", + "url": "https://api.github.com/repos/knpuniversity/oauth2-client-bundle/zipball/c38ca88a70aae3694ca346a41b13b9a8f6e33ed4", + "reference": "c38ca88a70aae3694ca346a41b13b9a8f6e33ed4", "shasum": "" }, "require": { "league/oauth2-client": "^2.0", "php": ">=8.1", - "symfony/dependency-injection": "^4.4|^5.0|^6.0|^7.0", - "symfony/framework-bundle": "^4.4|^5.0|^6.0|^7.0", - "symfony/http-foundation": "^4.4|^5.0|^6.0|^7.0", - "symfony/routing": "^4.4|^5.0|^6.0|^7.0" + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/framework-bundle": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0" }, "require-dev": { "league/oauth2-facebook": "^1.1|^2.0", - "phpstan/phpstan": "^1.0", - "symfony/phpunit-bridge": "^5.3.1|^6.0|^7.0", - "symfony/security-guard": "^4.4|^5.0|^6.0|^7.0", - "symfony/yaml": "^4.4|^5.0|^6.0|^7.0" + "symfony/phpunit-bridge": "^5.4|^6.0|^7.0", + "symfony/security-guard": "^5.4", + "symfony/yaml": "^5.4|^6.0|^7.0" }, "suggest": { "symfony/security-guard": "For integration with Symfony's Guard Security layer" @@ -4556,33 +4517,33 @@ ], "support": { "issues": "https://github.com/knpuniversity/oauth2-client-bundle/issues", - "source": "https://github.com/knpuniversity/oauth2-client-bundle/tree/v2.18.1" + "source": "https://github.com/knpuniversity/oauth2-client-bundle/tree/v2.18.3" }, - "time": "2024-02-14T17:41:28+00:00" + "time": "2024-10-02T14:26:09+00:00" }, { "name": "laminas/laminas-code", - "version": "4.14.0", + "version": "4.15.1", "source": { "type": "git", "url": "https://github.com/laminas/laminas-code.git", - "reference": "562e02b7d85cb9142b5116cc76c4c7c162a11a1c" + "reference": "877ad42fe9c164785182fca8afa3f416a056884d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-code/zipball/562e02b7d85cb9142b5116cc76c4c7c162a11a1c", - "reference": "562e02b7d85cb9142b5116cc76c4c7c162a11a1c", + "url": "https://api.github.com/repos/laminas/laminas-code/zipball/877ad42fe9c164785182fca8afa3f416a056884d", + "reference": "877ad42fe9c164785182fca8afa3f416a056884d", "shasum": "" }, "require": { - "php": "~8.1.0 || ~8.2.0 || ~8.3.0" + "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" }, "require-dev": { "doctrine/annotations": "^2.0.1", "ext-phar": "*", - "laminas/laminas-coding-standard": "^2.5.0", - "laminas/laminas-stdlib": "^3.17.0", - "phpunit/phpunit": "^10.3.3", + "laminas/laminas-coding-standard": "^3.0.0", + "laminas/laminas-stdlib": "^3.18.0", + "phpunit/phpunit": "^10.5.37", "psalm/plugin-phpunit": "^0.19.0", "vimeo/psalm": "^5.15.0" }, @@ -4621,7 +4582,7 @@ "type": "community_bridge" } ], - "time": "2024-06-17T08:50:25+00:00" + "time": "2024-10-25T10:15:16+00:00" }, { "name": "lcobucci/clock", @@ -5261,16 +5222,16 @@ }, { "name": "liip/imagine-bundle", - "version": "2.12.3", + "version": "2.13.2", "source": { "type": "git", "url": "https://github.com/liip/LiipImagineBundle.git", - "reference": "fa2baa6262bb74038f01ac746dc3e2a8bc441e09" + "reference": "98e0318ea0f7b9500343236e63cc29ded58a1d43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/liip/LiipImagineBundle/zipball/fa2baa6262bb74038f01ac746dc3e2a8bc441e09", - "reference": "fa2baa6262bb74038f01ac746dc3e2a8bc441e09", + "url": "https://api.github.com/repos/liip/LiipImagineBundle/zipball/98e0318ea0f7b9500343236e63cc29ded58a1d43", + "reference": "98e0318ea0f7b9500343236e63cc29ded58a1d43", "shasum": "" }, "require": { @@ -5296,6 +5257,7 @@ "phpstan/phpstan": "^1.10.0", "psr/cache": "^1.0|^2.0|^3.0", "psr/log": "^1.0", + "symfony/asset": "^3.4|^4.4|^5.3|^6.0|^7.0", "symfony/browser-kit": "^3.4|^4.4|^5.3|^6.0|^7.0", "symfony/cache": "^3.4|^4.4|^5.3|^6.0|^7.0", "symfony/console": "^3.4|^4.4|^5.3|^6.0|^7.0", @@ -5317,10 +5279,12 @@ "ext-gd": "required to use gd driver", "ext-gmagick": "required to use gmagick driver", "ext-imagick": "required to use imagick driver", + "ext-json": "required to read JSON manifest versioning", "ext-mongodb": "required for mongodb components", "league/flysystem": "required to use FlySystem data loader or cache resolver", "monolog/monolog": "A psr/log compatible logger is required to enable logging", "rokka/imagine-vips": "required to use 'vips' driver", + "symfony/asset": "If you want to use asset versioning", "symfony/messenger": "If you like to process images in background", "symfony/templating": "required to use deprecated Templating component instead of Twig" }, @@ -5358,9 +5322,9 @@ ], "support": { "issues": "https://github.com/liip/LiipImagineBundle/issues", - "source": "https://github.com/liip/LiipImagineBundle/tree/2.12.3" + "source": "https://github.com/liip/LiipImagineBundle/tree/2.13.2" }, - "time": "2024-05-16T08:51:30+00:00" + "time": "2024-09-04T12:55:26+00:00" }, { "name": "lorenzo/pinky", @@ -5484,16 +5448,16 @@ }, { "name": "monolog/monolog", - "version": "3.6.0", + "version": "3.7.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "4b18b21a5527a3d5ffdac2fd35d3ab25a9597654" + "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/4b18b21a5527a3d5ffdac2fd35d3ab25a9597654", - "reference": "4b18b21a5527a3d5ffdac2fd35d3ab25a9597654", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f4393b648b78a5408747de94fca38beb5f7e9ef8", + "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8", "shasum": "" }, "require": { @@ -5569,7 +5533,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.6.0" + "source": "https://github.com/Seldaek/monolog/tree/3.7.0" }, "funding": [ { @@ -5581,7 +5545,7 @@ "type": "tidelift" } ], - "time": "2024-04-12T21:02:21+00:00" + "time": "2024-06-28T09:40:51+00:00" }, { "name": "nbgrp/onelogin-saml-bundle", @@ -5721,16 +5685,16 @@ }, { "name": "nelmio/cors-bundle", - "version": "2.4.0", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/nelmio/NelmioCorsBundle.git", - "reference": "78fcdb91f76b080a1008133def9c7f613833933d" + "reference": "3a526fe025cd20e04a6a11370cf5ab28dbb5a544" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nelmio/NelmioCorsBundle/zipball/78fcdb91f76b080a1008133def9c7f613833933d", - "reference": "78fcdb91f76b080a1008133def9c7f613833933d", + "url": "https://api.github.com/repos/nelmio/NelmioCorsBundle/zipball/3a526fe025cd20e04a6a11370cf5ab28dbb5a544", + "reference": "3a526fe025cd20e04a6a11370cf5ab28dbb5a544", "shasum": "" }, "require": { @@ -5777,26 +5741,27 @@ ], "support": { "issues": "https://github.com/nelmio/NelmioCorsBundle/issues", - "source": "https://github.com/nelmio/NelmioCorsBundle/tree/2.4.0" + "source": "https://github.com/nelmio/NelmioCorsBundle/tree/2.5.0" }, - "time": "2023-11-30T16:41:19+00:00" + "time": "2024-06-24T21:25:28+00:00" }, { "name": "nelmio/security-bundle", - "version": "v3.3.0", + "version": "v3.4.2", "source": { "type": "git", "url": "https://github.com/nelmio/NelmioSecurityBundle.git", - "reference": "6a6c75ef6342385ef732f0b5afa705660177250f" + "reference": "3c4739628eafe886c001210aa0d97b33f3551599" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nelmio/NelmioSecurityBundle/zipball/6a6c75ef6342385ef732f0b5afa705660177250f", - "reference": "6a6c75ef6342385ef732f0b5afa705660177250f", + "url": "https://api.github.com/repos/nelmio/NelmioSecurityBundle/zipball/3c4739628eafe886c001210aa0d97b33f3551599", + "reference": "3c4739628eafe886c001210aa0d97b33f3551599", "shasum": "" }, "require": { "php": "^7.4 || ^8.0", + "symfony/deprecation-contracts": "^2.5 || ^3", "symfony/framework-bundle": "^5.4 || ^6.3 || ^7.0", "symfony/http-kernel": "^5.4 || ^6.3 || ^7.0", "symfony/security-core": "^5.4 || ^6.3 || ^7.0", @@ -5850,67 +5815,9 @@ ], "support": { "issues": "https://github.com/nelmio/NelmioSecurityBundle/issues", - "source": "https://github.com/nelmio/NelmioSecurityBundle/tree/v3.3.0" + "source": "https://github.com/nelmio/NelmioSecurityBundle/tree/v3.4.2" }, - "time": "2024-04-10T08:11:27+00:00" - }, - { - "name": "nikic/php-parser", - "version": "v5.0.2", - "source": { - "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13", - "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-json": "*", - "ext-tokenizer": "*", - "php": ">=7.4" - }, - "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" - }, - "bin": [ - "bin/php-parse" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, - "autoload": { - "psr-4": { - "PhpParser\\": "lib/PhpParser" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } - ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], - "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2" - }, - "time": "2024-03-05T20:51:40+00:00" + "time": "2024-09-10T13:22:26+00:00" }, { "name": "nikolaposa/version", @@ -5973,75 +5880,18 @@ }, "time": "2023-12-29T22:07:54+00:00" }, - { - "name": "nyholm/nsa", - "version": "1.3.0", - "source": { - "type": "git", - "url": "https://github.com/Nyholm/NSA.git", - "reference": "c264c17ed2aa8251c64ad289442ed53f64cdb283" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Nyholm/NSA/zipball/c264c17ed2aa8251c64ad289442ed53f64cdb283", - "reference": "c264c17ed2aa8251c64ad289442ed53f64cdb283", - "shasum": "" - }, - "require": { - "php": ">=7.1", - "webmozart/assert": "^1.1.0" - }, - "require-dev": { - "symfony/phpunit-bridge": "^4.4 || ^5.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "Nyholm\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com" - } - ], - "description": "See everything and do whatever you want. No privacy rule will stop us. Used in tests, debugging and fixtures to access properties and methods.", - "homepage": "https://tnyholm.se", - "keywords": [ - "Fixture", - "debug", - "reflection", - "test" - ], - "support": { - "issues": "https://github.com/Nyholm/NSA/issues", - "source": "https://github.com/Nyholm/NSA/tree/1.3.0" - }, - "funding": [ - { - "url": "https://github.com/nyholm", - "type": "github" - } - ], - "time": "2021-07-15T18:25:37+00:00" - }, { "name": "nyholm/psr7", - "version": "1.8.1", + "version": "1.8.2", "source": { "type": "git", "url": "https://github.com/Nyholm/psr7.git", - "reference": "aa5fc277a4f5508013d571341ade0c3886d4d00e" + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Nyholm/psr7/zipball/aa5fc277a4f5508013d571341ade0c3886d4d00e", - "reference": "aa5fc277a4f5508013d571341ade0c3886d4d00e", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3", "shasum": "" }, "require": { @@ -6094,7 +5944,7 @@ ], "support": { "issues": "https://github.com/Nyholm/psr7/issues", - "source": "https://github.com/Nyholm/psr7/tree/1.8.1" + "source": "https://github.com/Nyholm/psr7/tree/1.8.2" }, "funding": [ { @@ -6106,7 +5956,7 @@ "type": "github" } ], - "time": "2023-11-13T09:31:12+00:00" + "time": "2024-09-09T07:06:30+00:00" }, { "name": "omines/datatables-bundle", @@ -6530,16 +6380,16 @@ }, { "name": "php-http/discovery", - "version": "1.19.4", + "version": "1.20.0", "source": { "type": "git", "url": "https://github.com/php-http/discovery.git", - "reference": "0700efda8d7526335132360167315fdab3aeb599" + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/discovery/zipball/0700efda8d7526335132360167315fdab3aeb599", - "reference": "0700efda8d7526335132360167315fdab3aeb599", + "url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d", "shasum": "" }, "require": { @@ -6603,22 +6453,22 @@ ], "support": { "issues": "https://github.com/php-http/discovery/issues", - "source": "https://github.com/php-http/discovery/tree/1.19.4" + "source": "https://github.com/php-http/discovery/tree/1.20.0" }, - "time": "2024-03-29T13:00:05+00:00" + "time": "2024-10-02T11:20:13+00:00" }, { "name": "php-http/httplug", - "version": "2.4.0", + "version": "2.4.1", "source": { "type": "git", "url": "https://github.com/php-http/httplug.git", - "reference": "625ad742c360c8ac580fcc647a1541d29e257f67" + "reference": "5cad731844891a4c282f3f3e1b582c46839d22f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/httplug/zipball/625ad742c360c8ac580fcc647a1541d29e257f67", - "reference": "625ad742c360c8ac580fcc647a1541d29e257f67", + "url": "https://api.github.com/repos/php-http/httplug/zipball/5cad731844891a4c282f3f3e1b582c46839d22f4", + "reference": "5cad731844891a4c282f3f3e1b582c46839d22f4", "shasum": "" }, "require": { @@ -6660,9 +6510,9 @@ ], "support": { "issues": "https://github.com/php-http/httplug/issues", - "source": "https://github.com/php-http/httplug/tree/2.4.0" + "source": "https://github.com/php-http/httplug/tree/2.4.1" }, - "time": "2023-04-14T15:10:03+00:00" + "time": "2024-09-23T11:39:58+00:00" }, { "name": "php-http/promise", @@ -6716,236 +6566,6 @@ }, "time": "2024-03-15T13:55:21+00:00" }, - { - "name": "php-translation/common", - "version": "3.3.0", - "source": { - "type": "git", - "url": "https://github.com/php-translation/common.git", - "reference": "f5e0e36222bcf400310d089646f8cde1648ca974" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-translation/common/zipball/f5e0e36222bcf400310d089646f8cde1648ca974", - "reference": "f5e0e36222bcf400310d089646f8cde1648ca974", - "shasum": "" - }, - "require": { - "php": ">=7.2", - "symfony/translation": " ^3.4 || ^4.3 || ^5.0 || ^6.0 || ^7.0" - }, - "require-dev": { - "phpspec/prophecy-phpunit": "^2.1", - "phpunit/phpunit": ">=8.5.23", - "symfony/framework-bundle": " ^3.4 || ^4.3 || ^5.0 || ^6.0 || ^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Translation\\Common\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com" - } - ], - "description": "Common translation stuff", - "support": { - "issues": "https://github.com/php-translation/common/issues", - "source": "https://github.com/php-translation/common/tree/3.3.0" - }, - "time": "2024-01-29T16:29:12+00:00" - }, - { - "name": "php-translation/extractor", - "version": "2.2.0", - "source": { - "type": "git", - "url": "https://github.com/php-translation/extractor.git", - "reference": "44d869006c1131f0ebe1d358fd691621eacc3e45" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-translation/extractor/zipball/44d869006c1131f0ebe1d358fd691621eacc3e45", - "reference": "44d869006c1131f0ebe1d358fd691621eacc3e45", - "shasum": "" - }, - "require": { - "doctrine/annotations": "^1.7 || ^2.0", - "nikic/php-parser": "^4.0 || ^5.0", - "php": "^8.1", - "symfony/finder": "^5.4 || ^6.4 || ^7.0", - "twig/twig": "^2.0 || ^3.0" - }, - "require-dev": { - "knplabs/knp-menu": "^3.1", - "symfony/phpunit-bridge": "^5.4 || ^6.4 || ^7.0", - "symfony/translation": "^5.4 || ^6.4 || ^7.0", - "symfony/twig-bridge": "^5.4 || ^6.4 || ^7.0", - "symfony/validator": "^5.4 || ^6.4 || ^7.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "psr-4": { - "Translation\\Extractor\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com" - } - ], - "description": "Extract translations form the source code", - "support": { - "issues": "https://github.com/php-translation/extractor/issues", - "source": "https://github.com/php-translation/extractor/tree/2.2.0" - }, - "time": "2024-06-11T21:40:32+00:00" - }, - { - "name": "php-translation/symfony-bundle", - "version": "0.14.3", - "source": { - "type": "git", - "url": "https://github.com/php-translation/symfony-bundle.git", - "reference": "722e61673af494824b585b32bc619200b28729e4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-translation/symfony-bundle/zipball/722e61673af494824b585b32bc619200b28729e4", - "reference": "722e61673af494824b585b32bc619200b28729e4", - "shasum": "" - }, - "require": { - "nyholm/nsa": "^1.1", - "php": "^8.0", - "php-translation/extractor": "^2.0", - "php-translation/symfony-storage": "^2.1", - "symfony/asset": "^5.3 || ^6.0", - "symfony/console": "^5.3 || ^6.0", - "symfony/finder": "^5.3 || ^6.0", - "symfony/framework-bundle": "^5.3 || ^6.0", - "symfony/intl": "^5.3 || ^6.0", - "symfony/translation": "^5.3 || ^6.0", - "symfony/twig-bundle": "^5.3 || ^6.0", - "symfony/validator": "^5.3 || ^6.0", - "twig/twig": "^2.14.4 || ^3.3" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.3", - "matthiasnoback/symfony-config-test": "^4.1", - "matthiasnoback/symfony-dependency-injection-test": "^4.1", - "nyholm/psr7": "^1.1", - "nyholm/symfony-bundle-test": "^2.0", - "php-http/curl-client": "^1.7 || ^2.0", - "php-http/message": "^1.11", - "php-http/message-factory": "^1.0.2", - "php-translation/translator": "^1.0", - "symfony/dependency-injection": "^5.3 || ^6.0", - "symfony/phpunit-bridge": "^5.2 || ^6.0", - "symfony/twig-bridge": "^5.3 || ^6.0", - "symfony/web-profiler-bundle": "^5.3 || ^6.0" - }, - "suggest": { - "php-http/httplug-bundle": "To easier configure your httplug clients." - }, - "type": "symfony-bundle", - "extra": { - "branch-alias": { - "dev-master": "0.12-dev" - } - }, - "autoload": { - "psr-4": { - "Translation\\Bundle\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com" - } - ], - "support": { - "issues": "https://github.com/php-translation/symfony-bundle/issues", - "source": "https://github.com/php-translation/symfony-bundle/tree/0.14.3" - }, - "time": "2023-12-15T14:18:10+00:00" - }, - { - "name": "php-translation/symfony-storage", - "version": "2.4.0", - "source": { - "type": "git", - "url": "https://github.com/php-translation/symfony-storage.git", - "reference": "0f4702c5837802507231ee6649c49b57b6d7ab89" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-translation/symfony-storage/zipball/0f4702c5837802507231ee6649c49b57b6d7ab89", - "reference": "0f4702c5837802507231ee6649c49b57b6d7ab89", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "php-translation/common": "^3.0", - "symfony/translation": "^3.4 || ^4.2 || ^5.0 || ^6.0 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": ">=8.5.20", - "symfony/framework-bundle": " ^3.4 || ^4.2 || ^5.0 || ^6.0 || ^7.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.4-dev" - } - }, - "autoload": { - "psr-4": { - "Translation\\SymfonyStorage\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com" - } - ], - "description": "A translation file storage using Symfony translation component.", - "support": { - "issues": "https://github.com/php-translation/symfony-storage/issues", - "source": "https://github.com/php-translation/symfony-storage/tree/2.4.0" - }, - "time": "2024-01-29T16:41:31+00:00" - }, { "name": "phpdocumentor/reflection-common", "version": "2.2.0", @@ -7001,16 +6621,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.4.1", + "version": "5.5.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c" + "reference": "0c70d2c566e899666f367ab7b80986beb3581e6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", - "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/0c70d2c566e899666f367ab7b80986beb3581e6f", + "reference": "0c70d2c566e899666f367ab7b80986beb3581e6f", "shasum": "" }, "require": { @@ -7023,13 +6643,13 @@ "webmozart/assert": "^1.9.1" }, "require-dev": { - "mockery/mockery": "~1.3.5", + "mockery/mockery": "~1.3.5 || ~1.6.0", "phpstan/extension-installer": "^1.1", "phpstan/phpstan": "^1.8", "phpstan/phpstan-mockery": "^1.1", "phpstan/phpstan-webmozart-assert": "^1.2", "phpunit/phpunit": "^9.5", - "vimeo/psalm": "^5.13" + "psalm/phar": "^5.26" }, "type": "library", "extra": { @@ -7059,29 +6679,29 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.4.1" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.5.1" }, - "time": "2024-05-21T05:55:05+00:00" + "time": "2024-11-06T11:58:54+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.8.2", + "version": "1.9.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "153ae662783729388a584b4361f2545e4d841e3c" + "reference": "1fb5ba8d045f5dd984ebded5b1cc66f29459422d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", - "reference": "153ae662783729388a584b4361f2545e4d841e3c", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/1fb5ba8d045f5dd984ebded5b1cc66f29459422d", + "reference": "1fb5ba8d045f5dd984ebded5b1cc66f29459422d", "shasum": "" }, "require": { "doctrine/deprecations": "^1.0", "php": "^7.3 || ^8.0", "phpdocumentor/reflection-common": "^2.0", - "phpstan/phpdoc-parser": "^1.13" + "phpstan/phpdoc-parser": "^1.18" }, "require-dev": { "ext-tokenizer": "*", @@ -7117,22 +6737,22 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.9.0" }, - "time": "2024-02-23T11:10:43+00:00" + "time": "2024-11-03T20:11:34+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "1.29.1", + "version": "1.33.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4" + "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fcaefacf2d5c417e928405b71b400d4ce10daaf4", - "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/82a311fd3690fb2bf7b64d5c98f912b3dd746140", + "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140", "shasum": "" }, "require": { @@ -7164,9 +6784,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.29.1" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.33.0" }, - "time": "2024-05-31T08:52:43+00:00" + "time": "2024-10-13T11:25:22+00:00" }, { "name": "psr/cache", @@ -7586,16 +7206,16 @@ }, { "name": "psr/log", - "version": "3.0.0", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { @@ -7630,9 +7250,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "time": "2021-07-14T16:46:02+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { "name": "psr/simple-cache", @@ -7985,16 +7605,16 @@ }, { "name": "s9e/text-formatter", - "version": "2.17.3", + "version": "2.18.0", "source": { "type": "git", "url": "https://github.com/s9e/TextFormatter.git", - "reference": "9adf2557f13e45c4524d0dc9886cab22822e337a" + "reference": "4970711f25d94306b4835b723b9cc5010170ea37" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/9adf2557f13e45c4524d0dc9886cab22822e337a", - "reference": "9adf2557f13e45c4524d0dc9886cab22822e337a", + "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/4970711f25d94306b4835b723b9cc5010170ea37", + "reference": "4970711f25d94306b4835b723b9cc5010170ea37", "shasum": "" }, "require": { @@ -8022,7 +7642,7 @@ }, "type": "library", "extra": { - "version": "2.17.3" + "version": "2.18.0" }, "autoload": { "psr-4": { @@ -8054,30 +7674,30 @@ ], "support": { "issues": "https://github.com/s9e/TextFormatter/issues", - "source": "https://github.com/s9e/TextFormatter/tree/2.17.3" + "source": "https://github.com/s9e/TextFormatter/tree/2.18.0" }, - "time": "2024-05-26T00:00:08+00:00" + "time": "2024-07-24T14:50:52+00:00" }, { "name": "sabberworm/php-css-parser", - "version": "v8.5.1", + "version": "v8.7.0", "source": { "type": "git", "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", - "reference": "4a3d572b0f8b28bb6fd016ae8bbfc445facef152" + "reference": "f414ff953002a9b18e3a116f5e462c56f21237cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/4a3d572b0f8b28bb6fd016ae8bbfc445facef152", - "reference": "4a3d572b0f8b28bb6fd016ae8bbfc445facef152", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/f414ff953002a9b18e3a116f5e462c56f21237cf", + "reference": "f414ff953002a9b18e3a116f5e462c56f21237cf", "shasum": "" }, "require": { "ext-iconv": "*", - "php": ">=5.6.20" + "php": "^5.6.20 || ^7.0.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" }, "require-dev": { - "phpunit/phpunit": "^5.7.27" + "phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.40" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" @@ -8119,9 +7739,9 @@ ], "support": { "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", - "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.5.1" + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.7.0" }, - "time": "2024-02-15T16:41:13+00:00" + "time": "2024-10-27T17:38:32+00:00" }, { "name": "scheb/2fa-backup-code", @@ -8344,16 +7964,16 @@ }, { "name": "shivas/versioning-bundle", - "version": "4.1.0", + "version": "4.1.1", "source": { "type": "git", "url": "https://github.com/shivas/versioning-bundle.git", - "reference": "fd09c29bee656419d1273e4a00b13e0f8154a4c3" + "reference": "fd89e3501ff1b0d3e6abe61eb7a878d1d4746868" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/shivas/versioning-bundle/zipball/fd09c29bee656419d1273e4a00b13e0f8154a4c3", - "reference": "fd09c29bee656419d1273e4a00b13e0f8154a4c3", + "url": "https://api.github.com/repos/shivas/versioning-bundle/zipball/fd89e3501ff1b0d3e6abe61eb7a878d1d4746868", + "reference": "fd89e3501ff1b0d3e6abe61eb7a878d1d4746868", "shasum": "" }, "require": { @@ -8397,23 +8017,23 @@ ], "support": { "issues": "https://github.com/shivas/versioning-bundle/issues", - "source": "https://github.com/shivas/versioning-bundle/tree/4.1.0", + "source": "https://github.com/shivas/versioning-bundle/tree/4.1.1", "wiki": "https://github.com/shivas/versioning-bundle/wiki" }, - "time": "2024-03-16T16:50:44+00:00" + "time": "2024-08-14T19:33:15+00:00" }, { "name": "spatie/db-dumper", - "version": "3.6.0", + "version": "3.7.0", "source": { "type": "git", "url": "https://github.com/spatie/db-dumper.git", - "reference": "faca5056830bccea04eadf07e8074669cb9e905e" + "reference": "22553ab8c34a9bb70645cb9bc2d9f236f3135705" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/db-dumper/zipball/faca5056830bccea04eadf07e8074669cb9e905e", - "reference": "faca5056830bccea04eadf07e8074669cb9e905e", + "url": "https://api.github.com/repos/spatie/db-dumper/zipball/22553ab8c34a9bb70645cb9bc2d9f236f3135705", + "reference": "22553ab8c34a9bb70645cb9bc2d9f236f3135705", "shasum": "" }, "require": { @@ -8451,7 +8071,7 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/db-dumper/tree/3.6.0" + "source": "https://github.com/spatie/db-dumper/tree/3.7.0" }, "funding": [ { @@ -8463,20 +8083,20 @@ "type": "github" } ], - "time": "2024-04-24T14:54:13+00:00" + "time": "2024-09-23T08:58:35+00:00" }, { "name": "spomky-labs/cbor-php", - "version": "3.0.4", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/Spomky-Labs/cbor-php.git", - "reference": "658ed12a85a6b31fa312b89cd92f3a4ce6df4c6b" + "reference": "499d9bff0a6d59c4f1b813cc617fc3fd56d6dca4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/658ed12a85a6b31fa312b89cd92f3a4ce6df4c6b", - "reference": "658ed12a85a6b31fa312b89cd92f3a4ce6df4c6b", + "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/499d9bff0a6d59c4f1b813cc617fc3fd56d6dca4", + "reference": "499d9bff0a6d59c4f1b813cc617fc3fd56d6dca4", "shasum": "" }, "require": { @@ -8487,7 +8107,7 @@ "require-dev": { "ekino/phpstan-banned-code": "^1.0", "ext-json": "*", - "infection/infection": "^0.27", + "infection/infection": "^0.29", "php-parallel-lint/php-parallel-lint": "^1.3", "phpstan/extension-installer": "^1.1", "phpstan/phpstan": "^1.0", @@ -8495,9 +8115,9 @@ "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.0", "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/phpunit": "^10.1", - "qossmic/deptrac-shim": "^1.0", - "rector/rector": "^0.19", + "phpunit/phpunit": "^10.1|^11.0", + "qossmic/deptrac": "^2.0", + "rector/rector": "^1.0", "roave/security-advisories": "dev-latest", "symfony/var-dumper": "^6.0|^7.0", "symplify/easy-coding-standard": "^12.0" @@ -8534,7 +8154,7 @@ ], "support": { "issues": "https://github.com/Spomky-Labs/cbor-php/issues", - "source": "https://github.com/Spomky-Labs/cbor-php/tree/3.0.4" + "source": "https://github.com/Spomky-Labs/cbor-php/tree/3.1.0" }, "funding": [ { @@ -8546,7 +8166,7 @@ "type": "patreon" } ], - "time": "2024-01-29T20:33:48+00:00" + "time": "2024-07-18T08:37:03+00:00" }, { "name": "spomky-labs/otphp", @@ -8769,16 +8389,16 @@ }, { "name": "symfony/asset", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/asset.git", - "reference": "c668aa320e26b7379540368832b9d1dd43d32603" + "reference": "2466c17d61d14539cddf77e57ebb9cc971185302" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/asset/zipball/c668aa320e26b7379540368832b9d1dd43d32603", - "reference": "c668aa320e26b7379540368832b9d1dd43d32603", + "url": "https://api.github.com/repos/symfony/asset/zipball/2466c17d61d14539cddf77e57ebb9cc971185302", + "reference": "2466c17d61d14539cddf77e57ebb9cc971185302", "shasum": "" }, "require": { @@ -8818,7 +8438,7 @@ "description": "Manages URL generation and versioning of web assets such as CSS stylesheets, JavaScript files and image files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/asset/tree/v6.4.8" + "source": "https://github.com/symfony/asset/tree/v6.4.13" }, "funding": [ { @@ -8834,20 +8454,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-10-25T15:07:50+00:00" }, { "name": "symfony/cache", - "version": "v6.4.8", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "287142df5579ce223c485b3872df3efae8390984" + "reference": "36fb8aa88833708e9f29014b6f15fac051a8b613" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/287142df5579ce223c485b3872df3efae8390984", - "reference": "287142df5579ce223c485b3872df3efae8390984", + "url": "https://api.github.com/repos/symfony/cache/zipball/36fb8aa88833708e9f29014b6f15fac051a8b613", + "reference": "36fb8aa88833708e9f29014b6f15fac051a8b613", "shasum": "" }, "require": { @@ -8914,7 +8534,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v6.4.8" + "source": "https://github.com/symfony/cache/tree/v6.4.14" }, "funding": [ { @@ -8930,7 +8550,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-11-05T15:34:40+00:00" }, { "name": "symfony/cache-contracts", @@ -9010,16 +8630,16 @@ }, { "name": "symfony/clock", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/clock.git", - "reference": "7a4840efd17135cbd547e41ec49fb910ed4f8b98" + "reference": "b2bf55c4dd115003309eafa87ee7df9ed3dde81b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/clock/zipball/7a4840efd17135cbd547e41ec49fb910ed4f8b98", - "reference": "7a4840efd17135cbd547e41ec49fb910ed4f8b98", + "url": "https://api.github.com/repos/symfony/clock/zipball/b2bf55c4dd115003309eafa87ee7df9ed3dde81b", + "reference": "b2bf55c4dd115003309eafa87ee7df9ed3dde81b", "shasum": "" }, "require": { @@ -9064,7 +8684,7 @@ "time" ], "support": { - "source": "https://github.com/symfony/clock/tree/v6.4.8" + "source": "https://github.com/symfony/clock/tree/v6.4.13" }, "funding": [ { @@ -9080,20 +8700,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:51:39+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/config", - "version": "v6.4.8", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "12e7e52515ce37191b193cf3365903c4f3951e35" + "reference": "4e55e7e4ffddd343671ea972216d4509f46c22ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/12e7e52515ce37191b193cf3365903c4f3951e35", - "reference": "12e7e52515ce37191b193cf3365903c4f3951e35", + "url": "https://api.github.com/repos/symfony/config/zipball/4e55e7e4ffddd343671ea972216d4509f46c22ef", + "reference": "4e55e7e4ffddd343671ea972216d4509f46c22ef", "shasum": "" }, "require": { @@ -9139,7 +8759,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v6.4.8" + "source": "https://github.com/symfony/config/tree/v6.4.14" }, "funding": [ { @@ -9155,20 +8775,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-11-04T11:33:53+00:00" }, { "name": "symfony/console", - "version": "v6.4.8", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "be5854cee0e8c7b110f00d695d11debdfa1a2a91" + "reference": "897c2441ed4eec8a8a2c37b943427d24dba3f26b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/be5854cee0e8c7b110f00d695d11debdfa1a2a91", - "reference": "be5854cee0e8c7b110f00d695d11debdfa1a2a91", + "url": "https://api.github.com/repos/symfony/console/zipball/897c2441ed4eec8a8a2c37b943427d24dba3f26b", + "reference": "897c2441ed4eec8a8a2c37b943427d24dba3f26b", "shasum": "" }, "require": { @@ -9233,7 +8853,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.8" + "source": "https://github.com/symfony/console/tree/v6.4.14" }, "funding": [ { @@ -9249,20 +8869,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-11-05T15:34:40+00:00" }, { "name": "symfony/css-selector", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "4b61b02fe15db48e3687ce1c45ea385d1780fe08" + "reference": "cb23e97813c5837a041b73a6d63a9ddff0778f5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/4b61b02fe15db48e3687ce1c45ea385d1780fe08", - "reference": "4b61b02fe15db48e3687ce1c45ea385d1780fe08", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/cb23e97813c5837a041b73a6d63a9ddff0778f5e", + "reference": "cb23e97813c5837a041b73a6d63a9ddff0778f5e", "shasum": "" }, "require": { @@ -9298,7 +8918,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v6.4.8" + "source": "https://github.com/symfony/css-selector/tree/v6.4.13" }, "funding": [ { @@ -9314,20 +8934,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/dependency-injection", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "d3b618176e8c3a9e5772151c51eba0c52a0c771c" + "reference": "728ae8f4e190133ce99d6d5f0bc1e8c8bd7c7a96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/d3b618176e8c3a9e5772151c51eba0c52a0c771c", - "reference": "d3b618176e8c3a9e5772151c51eba0c52a0c771c", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/728ae8f4e190133ce99d6d5f0bc1e8c8bd7c7a96", + "reference": "728ae8f4e190133ce99d6d5f0bc1e8c8bd7c7a96", "shasum": "" }, "require": { @@ -9379,7 +8999,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.4.8" + "source": "https://github.com/symfony/dependency-injection/tree/v6.4.13" }, "funding": [ { @@ -9395,7 +9015,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-10-25T15:07:50+00:00" }, { "name": "symfony/deprecation-contracts", @@ -9466,16 +9086,16 @@ }, { "name": "symfony/doctrine-bridge", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-bridge.git", - "reference": "afbf291ccaf595c8ff6f4ed3943aa0ea479e4d04" + "reference": "402d5831a73217ea76ab7e032cc05045cd3fa678" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/afbf291ccaf595c8ff6f4ed3943aa0ea479e4d04", - "reference": "afbf291ccaf595c8ff6f4ed3943aa0ea479e4d04", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/402d5831a73217ea76ab7e032cc05045cd3fa678", + "reference": "402d5831a73217ea76ab7e032cc05045cd3fa678", "shasum": "" }, "require": { @@ -9554,7 +9174,7 @@ "description": "Provides integration for Doctrine with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/doctrine-bridge/tree/v6.4.8" + "source": "https://github.com/symfony/doctrine-bridge/tree/v6.4.13" }, "funding": [ { @@ -9570,20 +9190,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-10-18T09:23:39+00:00" }, { "name": "symfony/dotenv", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/dotenv.git", - "reference": "55aefa0029adff89ecffdb560820e945c7983f06" + "reference": "436ae2dd89360fea8c7d5ff3f48ecf523c80bfb4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dotenv/zipball/55aefa0029adff89ecffdb560820e945c7983f06", - "reference": "55aefa0029adff89ecffdb560820e945c7983f06", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/436ae2dd89360fea8c7d5ff3f48ecf523c80bfb4", + "reference": "436ae2dd89360fea8c7d5ff3f48ecf523c80bfb4", "shasum": "" }, "require": { @@ -9628,7 +9248,7 @@ "environment" ], "support": { - "source": "https://github.com/symfony/dotenv/tree/v6.4.8" + "source": "https://github.com/symfony/dotenv/tree/v6.4.13" }, "funding": [ { @@ -9644,20 +9264,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-28T07:43:51+00:00" }, { "name": "symfony/error-handler", - "version": "v6.4.8", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "ef836152bf13472dc5fb5b08b0c0c4cfeddc0fcc" + "reference": "9e024324511eeb00983ee76b9aedc3e6ecd993d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/ef836152bf13472dc5fb5b08b0c0c4cfeddc0fcc", - "reference": "ef836152bf13472dc5fb5b08b0c0c4cfeddc0fcc", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/9e024324511eeb00983ee76b9aedc3e6ecd993d9", + "reference": "9e024324511eeb00983ee76b9aedc3e6ecd993d9", "shasum": "" }, "require": { @@ -9703,7 +9323,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.4.8" + "source": "https://github.com/symfony/error-handler/tree/v6.4.14" }, "funding": [ { @@ -9719,20 +9339,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-11-05T15:34:40+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "8d7507f02b06e06815e56bb39aa0128e3806208b" + "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/8d7507f02b06e06815e56bb39aa0128e3806208b", - "reference": "8d7507f02b06e06815e56bb39aa0128e3806208b", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", + "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", "shasum": "" }, "require": { @@ -9783,7 +9403,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.8" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.13" }, "funding": [ { @@ -9799,7 +9419,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -9879,16 +9499,16 @@ }, { "name": "symfony/expression-language", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/expression-language.git", - "reference": "0b63cb437741a42104d3ccc9bf60bbd8e1acbd2a" + "reference": "3524904fb026356a5230cd197f9a4e6a61e0e7df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/expression-language/zipball/0b63cb437741a42104d3ccc9bf60bbd8e1acbd2a", - "reference": "0b63cb437741a42104d3ccc9bf60bbd8e1acbd2a", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/3524904fb026356a5230cd197f9a4e6a61e0e7df", + "reference": "3524904fb026356a5230cd197f9a4e6a61e0e7df", "shasum": "" }, "require": { @@ -9923,7 +9543,7 @@ "description": "Provides an engine that can compile and evaluate expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/expression-language/tree/v6.4.8" + "source": "https://github.com/symfony/expression-language/tree/v6.4.13" }, "funding": [ { @@ -9939,20 +9559,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-10-09T08:40:40+00:00" }, { "name": "symfony/filesystem", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "4d37529150e7081c51b3c5d5718c55a04a9503f3" + "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/4d37529150e7081c51b3c5d5718c55a04a9503f3", - "reference": "4d37529150e7081c51b3c5d5718c55a04a9503f3", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/4856c9cf585d5a0313d8d35afd681a526f038dd3", + "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3", "shasum": "" }, "require": { @@ -9989,7 +9609,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.4.8" + "source": "https://github.com/symfony/filesystem/tree/v6.4.13" }, "funding": [ { @@ -10005,20 +9625,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-10-25T15:07:50+00:00" }, { "name": "symfony/finder", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "3ef977a43883215d560a2cecb82ec8e62131471c" + "reference": "daea9eca0b08d0ed1dc9ab702a46128fd1be4958" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/3ef977a43883215d560a2cecb82ec8e62131471c", - "reference": "3ef977a43883215d560a2cecb82ec8e62131471c", + "url": "https://api.github.com/repos/symfony/finder/zipball/daea9eca0b08d0ed1dc9ab702a46128fd1be4958", + "reference": "daea9eca0b08d0ed1dc9ab702a46128fd1be4958", "shasum": "" }, "require": { @@ -10053,7 +9673,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.4.8" + "source": "https://github.com/symfony/finder/tree/v6.4.13" }, "funding": [ { @@ -10069,26 +9689,29 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-10-01T08:30:56+00:00" }, { "name": "symfony/flex", - "version": "v2.4.5", + "version": "v2.4.7", "source": { "type": "git", "url": "https://github.com/symfony/flex.git", - "reference": "b0a405f40614c9f584b489d54f91091817b0e26e" + "reference": "92f4fba342161ff36072bd3b8e0b3c6c23160402" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/flex/zipball/b0a405f40614c9f584b489d54f91091817b0e26e", - "reference": "b0a405f40614c9f584b489d54f91091817b0e26e", + "url": "https://api.github.com/repos/symfony/flex/zipball/92f4fba342161ff36072bd3b8e0b3c6c23160402", + "reference": "92f4fba342161ff36072bd3b8e0b3c6c23160402", "shasum": "" }, "require": { "composer-plugin-api": "^2.1", "php": ">=8.0" }, + "conflict": { + "composer/semver": "<1.7.2" + }, "require-dev": { "composer/composer": "^2.1", "symfony/dotenv": "^5.4|^6.0", @@ -10118,7 +9741,7 @@ "description": "Composer plugin for Symfony", "support": { "issues": "https://github.com/symfony/flex/issues", - "source": "https://github.com/symfony/flex/tree/v2.4.5" + "source": "https://github.com/symfony/flex/tree/v2.4.7" }, "funding": [ { @@ -10134,20 +9757,20 @@ "type": "tidelift" } ], - "time": "2024-03-02T08:16:47+00:00" + "time": "2024-10-07T08:51:54+00:00" }, { "name": "symfony/form", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/form.git", - "reference": "196ebc738e59bec2bbf1f49c24cc221b47f77f5d" + "reference": "0fe17f90af23908ddc11dc23507db98e66572046" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/form/zipball/196ebc738e59bec2bbf1f49c24cc221b47f77f5d", - "reference": "196ebc738e59bec2bbf1f49c24cc221b47f77f5d", + "url": "https://api.github.com/repos/symfony/form/zipball/0fe17f90af23908ddc11dc23507db98e66572046", + "reference": "0fe17f90af23908ddc11dc23507db98e66572046", "shasum": "" }, "require": { @@ -10215,7 +9838,7 @@ "description": "Allows to easily create, process and reuse HTML forms", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/form/tree/v6.4.8" + "source": "https://github.com/symfony/form/tree/v6.4.13" }, "funding": [ { @@ -10231,20 +9854,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-10-09T08:40:40+00:00" }, { "name": "symfony/framework-bundle", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "7c7739f87f1a8be1c2f5e7d28addfe763a917acb" + "reference": "e8b0bd921f9bd35ea4d1508067c3f3f6e2036418" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/7c7739f87f1a8be1c2f5e7d28addfe763a917acb", - "reference": "7c7739f87f1a8be1c2f5e7d28addfe763a917acb", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/e8b0bd921f9bd35ea4d1508067c3f3f6e2036418", + "reference": "e8b0bd921f9bd35ea4d1508067c3f3f6e2036418", "shasum": "" }, "require": { @@ -10253,7 +9876,7 @@ "php": ">=8.1", "symfony/cache": "^5.4|^6.0|^7.0", "symfony/config": "^6.1|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4.12|^7.0", "symfony/deprecation-contracts": "^2.5|^3", "symfony/error-handler": "^6.1|^7.0", "symfony/event-dispatcher": "^5.4|^6.0|^7.0", @@ -10283,6 +9906,7 @@ "symfony/mime": "<6.4", "symfony/property-access": "<5.4", "symfony/property-info": "<5.4", + "symfony/runtime": "<5.4.45|>=6.0,<6.4.13|>=7.0,<7.1.6", "symfony/scheduler": "<6.4.4|>=7.0.0,<7.0.4", "symfony/security-core": "<5.4", "symfony/security-csrf": "<5.4", @@ -10363,7 +9987,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.4.8" + "source": "https://github.com/symfony/framework-bundle/tree/v6.4.13" }, "funding": [ { @@ -10379,20 +10003,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-10-25T15:07:50+00:00" }, { "name": "symfony/http-client", - "version": "v6.4.8", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "61faba993e620fc22d4f0ab3b6bcf8fbb0d44b05" + "reference": "05d88cbd816ad6e0202edd9a9963cb9d615b8826" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/61faba993e620fc22d4f0ab3b6bcf8fbb0d44b05", - "reference": "61faba993e620fc22d4f0ab3b6bcf8fbb0d44b05", + "url": "https://api.github.com/repos/symfony/http-client/zipball/05d88cbd816ad6e0202edd9a9963cb9d615b8826", + "reference": "05d88cbd816ad6e0202edd9a9963cb9d615b8826", "shasum": "" }, "require": { @@ -10456,7 +10080,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.4.8" + "source": "https://github.com/symfony/http-client/tree/v6.4.14" }, "funding": [ { @@ -10472,7 +10096,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-11-05T16:39:55+00:00" }, { "name": "symfony/http-client-contracts", @@ -10554,16 +10178,16 @@ }, { "name": "symfony/http-foundation", - "version": "v6.4.8", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "27de8cc95e11db7a50b027e71caaab9024545947" + "reference": "ba020a321a95519303a3f09ec2824d34d601c388" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/27de8cc95e11db7a50b027e71caaab9024545947", - "reference": "27de8cc95e11db7a50b027e71caaab9024545947", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ba020a321a95519303a3f09ec2824d34d601c388", + "reference": "ba020a321a95519303a3f09ec2824d34d601c388", "shasum": "" }, "require": { @@ -10611,7 +10235,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.4.8" + "source": "https://github.com/symfony/http-foundation/tree/v6.4.14" }, "funding": [ { @@ -10627,20 +10251,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-11-05T16:39:55+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.4.8", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "6c519aa3f32adcfd1d1f18d923f6b227d9acf3c1" + "reference": "8278a947d0369754a47b758a9e17b72cab970951" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6c519aa3f32adcfd1d1f18d923f6b227d9acf3c1", - "reference": "6c519aa3f32adcfd1d1f18d923f6b227d9acf3c1", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/8278a947d0369754a47b758a9e17b72cab970951", + "reference": "8278a947d0369754a47b758a9e17b72cab970951", "shasum": "" }, "require": { @@ -10725,7 +10349,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.4.8" + "source": "https://github.com/symfony/http-kernel/tree/v6.4.14" }, "funding": [ { @@ -10741,20 +10365,20 @@ "type": "tidelift" } ], - "time": "2024-06-02T16:06:25+00:00" + "time": "2024-11-06T09:45:21+00:00" }, { "name": "symfony/intl", - "version": "v6.4.8", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/intl.git", - "reference": "50265cdcf5a44bec3fcf487b5d0015aece91d1eb" + "reference": "4e852ff0b0f9851a7ca543ff731686d9a46574d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/intl/zipball/50265cdcf5a44bec3fcf487b5d0015aece91d1eb", - "reference": "50265cdcf5a44bec3fcf487b5d0015aece91d1eb", + "url": "https://api.github.com/repos/symfony/intl/zipball/4e852ff0b0f9851a7ca543ff731686d9a46574d1", + "reference": "4e852ff0b0f9851a7ca543ff731686d9a46574d1", "shasum": "" }, "require": { @@ -10808,7 +10432,7 @@ "localization" ], "support": { - "source": "https://github.com/symfony/intl/tree/v6.4.8" + "source": "https://github.com/symfony/intl/tree/v6.4.14" }, "funding": [ { @@ -10824,20 +10448,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-11-05T15:34:40+00:00" }, { "name": "symfony/mailer", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "76326421d44c07f7824b19487cfbf87870b37efc" + "reference": "c2f7e0d8d7ac8fe25faccf5d8cac462805db2663" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/76326421d44c07f7824b19487cfbf87870b37efc", - "reference": "76326421d44c07f7824b19487cfbf87870b37efc", + "url": "https://api.github.com/repos/symfony/mailer/zipball/c2f7e0d8d7ac8fe25faccf5d8cac462805db2663", + "reference": "c2f7e0d8d7ac8fe25faccf5d8cac462805db2663", "shasum": "" }, "require": { @@ -10888,7 +10512,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v6.4.8" + "source": "https://github.com/symfony/mailer/tree/v6.4.13" }, "funding": [ { @@ -10904,20 +10528,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/mime", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "618597ab8b78ac86d1c75a9d0b35540cda074f33" + "reference": "1de1cf14d99b12c7ebbb850491ec6ae3ed468855" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/618597ab8b78ac86d1c75a9d0b35540cda074f33", - "reference": "618597ab8b78ac86d1c75a9d0b35540cda074f33", + "url": "https://api.github.com/repos/symfony/mime/zipball/1de1cf14d99b12c7ebbb850491ec6ae3ed468855", + "reference": "1de1cf14d99b12c7ebbb850491ec6ae3ed468855", "shasum": "" }, "require": { @@ -10931,7 +10555,7 @@ "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", "symfony/mailer": "<5.4", - "symfony/serializer": "<6.3.2" + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3.1|^4", @@ -10941,7 +10565,7 @@ "symfony/process": "^5.4|^6.4|^7.0", "symfony/property-access": "^5.4|^6.0|^7.0", "symfony/property-info": "^5.4|^6.0|^7.0", - "symfony/serializer": "^6.3.2|^7.0" + "symfony/serializer": "^6.4.3|^7.0.3" }, "type": "library", "autoload": { @@ -10973,7 +10597,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.4.8" + "source": "https://github.com/symfony/mime/tree/v6.4.13" }, "funding": [ { @@ -10989,20 +10613,20 @@ "type": "tidelift" } ], - "time": "2024-06-01T07:50:16+00:00" + "time": "2024-10-25T15:07:50+00:00" }, { "name": "symfony/monolog-bridge", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/monolog-bridge.git", - "reference": "0fbee64913b1c595e7650a1919ba3edba8d49ea7" + "reference": "9d14621e59f22c2b6d030d92d37ffe5ae1e60452" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/0fbee64913b1c595e7650a1919ba3edba8d49ea7", - "reference": "0fbee64913b1c595e7650a1919ba3edba8d49ea7", + "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/9d14621e59f22c2b6d030d92d37ffe5ae1e60452", + "reference": "9d14621e59f22c2b6d030d92d37ffe5ae1e60452", "shasum": "" }, "require": { @@ -11052,7 +10676,7 @@ "description": "Provides integration for Monolog with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/monolog-bridge/tree/v6.4.8" + "source": "https://github.com/symfony/monolog-bridge/tree/v6.4.13" }, "funding": [ { @@ -11068,7 +10692,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-10-14T08:49:08+00:00" }, { "name": "symfony/monolog-bundle", @@ -11153,16 +10777,16 @@ }, { "name": "symfony/options-resolver", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "22ab9e9101ab18de37839074f8a1197f55590c1b" + "reference": "0a62a9f2504a8dd27083f89d21894ceb01cc59db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/22ab9e9101ab18de37839074f8a1197f55590c1b", - "reference": "22ab9e9101ab18de37839074f8a1197f55590c1b", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/0a62a9f2504a8dd27083f89d21894ceb01cc59db", + "reference": "0a62a9f2504a8dd27083f89d21894ceb01cc59db", "shasum": "" }, "require": { @@ -11200,7 +10824,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v6.4.8" + "source": "https://github.com/symfony/options-resolver/tree/v6.4.13" }, "funding": [ { @@ -11216,20 +10840,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/password-hasher", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/password-hasher.git", - "reference": "90ebbe946e5d64a5fad9ac9427e335045cf2bd31" + "reference": "e97a1b31f60b8bdfc1fdedab4398538da9441d47" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/password-hasher/zipball/90ebbe946e5d64a5fad9ac9427e335045cf2bd31", - "reference": "90ebbe946e5d64a5fad9ac9427e335045cf2bd31", + "url": "https://api.github.com/repos/symfony/password-hasher/zipball/e97a1b31f60b8bdfc1fdedab4398538da9441d47", + "reference": "e97a1b31f60b8bdfc1fdedab4398538da9441d47", "shasum": "" }, "require": { @@ -11272,7 +10896,7 @@ "password" ], "support": { - "source": "https://github.com/symfony/password-hasher/tree/v6.4.8" + "source": "https://github.com/symfony/password-hasher/tree/v6.4.13" }, "funding": [ { @@ -11288,24 +10912,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.29.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", - "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -11351,7 +10975,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" }, "funding": [ { @@ -11367,24 +10991,24 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.29.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f" + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f", - "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -11429,7 +11053,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" }, "funding": [ { @@ -11445,24 +11069,24 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-icu", - "version": "v1.29.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-icu.git", - "reference": "07094a28851a49107f3ab4f9120ca2975a64b6e1" + "reference": "d80a05e9904d2c2b9b95929f3e4b5d3a8f418d78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/07094a28851a49107f3ab4f9120ca2975a64b6e1", - "reference": "07094a28851a49107f3ab4f9120ca2975a64b6e1", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/d80a05e9904d2c2b9b95929f3e4b5d3a8f418d78", + "reference": "d80a05e9904d2c2b9b95929f3e4b5d3a8f418d78", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance and support of other locales than \"en\"" @@ -11513,7 +11137,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.31.0" }, "funding": [ { @@ -11529,26 +11153,25 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:12:16+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.29.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "a287ed7475f85bf6f61890146edbc932c0fff919" + "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/a287ed7475f85bf6f61890146edbc932c0fff919", - "reference": "a287ed7475f85bf6f61890146edbc932c0fff919", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773", + "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773", "shasum": "" }, "require": { - "php": ">=7.1", - "symfony/polyfill-intl-normalizer": "^1.10", - "symfony/polyfill-php72": "^1.10" + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" }, "suggest": { "ext-intl": "For best performance" @@ -11597,7 +11220,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0" }, "funding": [ { @@ -11613,24 +11236,24 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.29.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "bc45c394692b948b4d383a08d7753968bed9a83d" + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d", - "reference": "bc45c394692b948b4d383a08d7753968bed9a83d", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -11678,7 +11301,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" }, "funding": [ { @@ -11694,24 +11317,24 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.29.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -11758,7 +11381,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" }, "funding": [ { @@ -11774,97 +11397,24 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" - }, - { - "name": "symfony/polyfill-php72", - "version": "v1.29.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "861391a8da9a04cbad2d232ddd9e4893220d6e25" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/861391a8da9a04cbad2d232ddd9e4893220d6e25", - "reference": "861391a8da9a04cbad2d232ddd9e4893220d6e25", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.29.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.29.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", - "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -11911,7 +11461,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" }, "funding": [ { @@ -11927,24 +11477,100 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/polyfill-php82", - "version": "v1.29.0", + "name": "symfony/polyfill-php81", + "version": "v1.31.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php82.git", - "reference": "559d488c38784112c78b9bf17c5ce8366a265643" + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/559d488c38784112c78b9bf17c5ce8366a265643", - "reference": "559d488c38784112c78b9bf17c5ce8366a265643", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php82", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php82.git", + "reference": "5d2ed36f7734637dacc025f179698031951b1692" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/5d2ed36f7734637dacc025f179698031951b1692", + "reference": "5d2ed36f7734637dacc025f179698031951b1692", + "shasum": "" + }, + "require": { + "php": ">=7.2" }, "type": "library", "extra": { @@ -11987,7 +11613,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php82/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php82/tree/v1.31.0" }, "funding": [ { @@ -12003,25 +11629,24 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.29.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "86fcae159633351e5fd145d1c47de6c528f8caff" + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/86fcae159633351e5fd145d1c47de6c528f8caff", - "reference": "86fcae159633351e5fd145d1c47de6c528f8caff", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", "shasum": "" }, "require": { - "php": ">=7.1", - "symfony/polyfill-php80": "^1.14" + "php": ">=7.2" }, "type": "library", "extra": { @@ -12064,7 +11689,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0" }, "funding": [ { @@ -12080,24 +11705,24 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-uuid", - "version": "v1.29.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", - "reference": "3abdd21b0ceaa3000ee950097bc3cf9efc137853" + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/3abdd21b0ceaa3000ee950097bc3cf9efc137853", - "reference": "3abdd21b0ceaa3000ee950097bc3cf9efc137853", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-uuid": "*" @@ -12143,7 +11768,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.31.0" }, "funding": [ { @@ -12159,20 +11784,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/process", - "version": "v6.4.8", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "8d92dd79149f29e89ee0f480254db595f6a6a2c5" + "reference": "25214adbb0996d18112548de20c281be9f27279f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/8d92dd79149f29e89ee0f480254db595f6a6a2c5", - "reference": "8d92dd79149f29e89ee0f480254db595f6a6a2c5", + "url": "https://api.github.com/repos/symfony/process/zipball/25214adbb0996d18112548de20c281be9f27279f", + "reference": "25214adbb0996d18112548de20c281be9f27279f", "shasum": "" }, "require": { @@ -12204,7 +11829,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.4.8" + "source": "https://github.com/symfony/process/tree/v6.4.14" }, "funding": [ { @@ -12220,20 +11845,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-11-06T09:25:01+00:00" }, { "name": "symfony/property-access", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", - "reference": "e4d9b00983612f9c0013ca37c61affdba2dd975a" + "reference": "8cc779d88d12e440adaa26387bcfc25744064afe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/e4d9b00983612f9c0013ca37c61affdba2dd975a", - "reference": "e4d9b00983612f9c0013ca37c61affdba2dd975a", + "url": "https://api.github.com/repos/symfony/property-access/zipball/8cc779d88d12e440adaa26387bcfc25744064afe", + "reference": "8cc779d88d12e440adaa26387bcfc25744064afe", "shasum": "" }, "require": { @@ -12281,7 +11906,7 @@ "reflection" ], "support": { - "source": "https://github.com/symfony/property-access/tree/v6.4.8" + "source": "https://github.com/symfony/property-access/tree/v6.4.13" }, "funding": [ { @@ -12297,20 +11922,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/property-info", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "7f544bc6ceb1a6a2283c7af8e8621262c43b7ede" + "reference": "ea388133aadf407dfa54092873d1b7e52f439515" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/7f544bc6ceb1a6a2283c7af8e8621262c43b7ede", - "reference": "7f544bc6ceb1a6a2283c7af8e8621262c43b7ede", + "url": "https://api.github.com/repos/symfony/property-info/zipball/ea388133aadf407dfa54092873d1b7e52f439515", + "reference": "ea388133aadf407dfa54092873d1b7e52f439515", "shasum": "" }, "require": { @@ -12364,7 +11989,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v6.4.8" + "source": "https://github.com/symfony/property-info/tree/v6.4.13" }, "funding": [ { @@ -12380,20 +12005,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/proxy-manager-bridge", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/proxy-manager-bridge.git", - "reference": "b8119e0b248ef0711c25cd09acc729102122621c" + "reference": "8932b572e147e80fb498045c580eb14215197529" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/proxy-manager-bridge/zipball/b8119e0b248ef0711c25cd09acc729102122621c", - "reference": "b8119e0b248ef0711c25cd09acc729102122621c", + "url": "https://api.github.com/repos/symfony/proxy-manager-bridge/zipball/8932b572e147e80fb498045c580eb14215197529", + "reference": "8932b572e147e80fb498045c580eb14215197529", "shasum": "" }, "require": { @@ -12431,7 +12056,7 @@ "description": "Provides integration for ProxyManager with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/proxy-manager-bridge/tree/v6.4.8" + "source": "https://github.com/symfony/proxy-manager-bridge/tree/v6.4.13" }, "funding": [ { @@ -12447,47 +12072,42 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/psr-http-message-bridge", - "version": "v2.3.1", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/psr-http-message-bridge.git", - "reference": "581ca6067eb62640de5ff08ee1ba6850a0ee472e" + "reference": "c9cf83326a1074f83a738fc5320945abf7fb7fec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/581ca6067eb62640de5ff08ee1ba6850a0ee472e", - "reference": "581ca6067eb62640de5ff08ee1ba6850a0ee472e", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/c9cf83326a1074f83a738fc5320945abf7fb7fec", + "reference": "c9cf83326a1074f83a738fc5320945abf7fb7fec", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/http-message": "^1.0 || ^2.0", - "symfony/deprecation-contracts": "^2.5 || ^3.0", - "symfony/http-foundation": "^5.4 || ^6.0" + "php": ">=8.1", + "psr/http-message": "^1.0|^2.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0" + }, + "conflict": { + "php-http/discovery": "<1.15", + "symfony/http-kernel": "<6.2" }, "require-dev": { "nyholm/psr7": "^1.1", - "psr/log": "^1.1 || ^2 || ^3", - "symfony/browser-kit": "^5.4 || ^6.0", - "symfony/config": "^5.4 || ^6.0", - "symfony/event-dispatcher": "^5.4 || ^6.0", - "symfony/framework-bundle": "^5.4 || ^6.0", - "symfony/http-kernel": "^5.4 || ^6.0", - "symfony/phpunit-bridge": "^6.2" - }, - "suggest": { - "nyholm/psr7": "For a super lightweight PSR-7/17 implementation" + "php-http/discovery": "^1.15", + "psr/log": "^1.1.4|^2|^3", + "symfony/browser-kit": "^5.4|^6.0|^7.0", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/framework-bundle": "^6.2|^7.0", + "symfony/http-kernel": "^6.2|^7.0" }, "type": "symfony-bridge", - "extra": { - "branch-alias": { - "dev-main": "2.3-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Bridge\\PsrHttpMessage\\": "" @@ -12507,11 +12127,11 @@ }, { "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" + "homepage": "https://symfony.com/contributors" } ], "description": "PSR HTTP message bridge", - "homepage": "http://symfony.com", + "homepage": "https://symfony.com", "keywords": [ "http", "http-message", @@ -12519,8 +12139,7 @@ "psr-7" ], "support": { - "issues": "https://github.com/symfony/psr-http-message-bridge/issues", - "source": "https://github.com/symfony/psr-http-message-bridge/tree/v2.3.1" + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v6.4.13" }, "funding": [ { @@ -12536,20 +12155,20 @@ "type": "tidelift" } ], - "time": "2023-07-26T11:53:26+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/rate-limiter", - "version": "v6.4.8", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/rate-limiter.git", - "reference": "d96117211cf6740080827ee8c9eaf7e370243b50" + "reference": "e5898ab217ee766258ab41aa53556e1a029f7d04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/rate-limiter/zipball/d96117211cf6740080827ee8c9eaf7e370243b50", - "reference": "d96117211cf6740080827ee8c9eaf7e370243b50", + "url": "https://api.github.com/repos/symfony/rate-limiter/zipball/e5898ab217ee766258ab41aa53556e1a029f7d04", + "reference": "e5898ab217ee766258ab41aa53556e1a029f7d04", "shasum": "" }, "require": { @@ -12591,7 +12210,7 @@ "rate-limiter" ], "support": { - "source": "https://github.com/symfony/rate-limiter/tree/v6.4.8" + "source": "https://github.com/symfony/rate-limiter/tree/v6.4.14" }, "funding": [ { @@ -12607,20 +12226,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-11-05T15:34:40+00:00" }, { "name": "symfony/routing", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "8a40d0f9b01f0fbb80885d3ce0ad6714fb603a58" + "reference": "640a74250d13f9c30d5ca045b6aaaabcc8215278" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/8a40d0f9b01f0fbb80885d3ce0ad6714fb603a58", - "reference": "8a40d0f9b01f0fbb80885d3ce0ad6714fb603a58", + "url": "https://api.github.com/repos/symfony/routing/zipball/640a74250d13f9c30d5ca045b6aaaabcc8215278", + "reference": "640a74250d13f9c30d5ca045b6aaaabcc8215278", "shasum": "" }, "require": { @@ -12674,7 +12293,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.4.8" + "source": "https://github.com/symfony/routing/tree/v6.4.13" }, "funding": [ { @@ -12690,20 +12309,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-10-01T08:30:56+00:00" }, { "name": "symfony/runtime", - "version": "v6.4.8", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/runtime.git", - "reference": "b4bfa2fd4cad1fee62f80b3dfe4eb674cc3302a0" + "reference": "4facd4174f45cd37c65860403412b67c7381136a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/runtime/zipball/b4bfa2fd4cad1fee62f80b3dfe4eb674cc3302a0", - "reference": "b4bfa2fd4cad1fee62f80b3dfe4eb674cc3302a0", + "url": "https://api.github.com/repos/symfony/runtime/zipball/4facd4174f45cd37c65860403412b67c7381136a", + "reference": "4facd4174f45cd37c65860403412b67c7381136a", "shasum": "" }, "require": { @@ -12753,7 +12372,7 @@ "runtime" ], "support": { - "source": "https://github.com/symfony/runtime/tree/v6.4.8" + "source": "https://github.com/symfony/runtime/tree/v6.4.14" }, "funding": [ { @@ -12769,20 +12388,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-11-05T16:39:55+00:00" }, { "name": "symfony/security-bundle", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/security-bundle.git", - "reference": "dfb286069b0332e1f1c21962133d17c0fbc1e5e7" + "reference": "181d1fcf5f88ef8212ed7f6434e5ff51c9d7dff3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-bundle/zipball/dfb286069b0332e1f1c21962133d17c0fbc1e5e7", - "reference": "dfb286069b0332e1f1c21962133d17c0fbc1e5e7", + "url": "https://api.github.com/repos/symfony/security-bundle/zipball/181d1fcf5f88ef8212ed7f6434e5ff51c9d7dff3", + "reference": "181d1fcf5f88ef8212ed7f6434e5ff51c9d7dff3", "shasum": "" }, "require": { @@ -12791,7 +12410,7 @@ "php": ">=8.1", "symfony/clock": "^6.3|^7.0", "symfony/config": "^6.1|^7.0", - "symfony/dependency-injection": "^6.2|^7.0", + "symfony/dependency-injection": "^6.4.11|^7.1.4", "symfony/deprecation-contracts": "^2.5|^3", "symfony/event-dispatcher": "^5.4|^6.0|^7.0", "symfony/http-foundation": "^6.2|^7.0", @@ -12865,7 +12484,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.4.8" + "source": "https://github.com/symfony/security-bundle/tree/v6.4.13" }, "funding": [ { @@ -12881,20 +12500,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/security-core", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/security-core.git", - "reference": "5fc7850ada5e8e03d78c1739c82c64d5e2f7d495" + "reference": "bbd1a919aec8696a95bf8749d5577fbe74de973c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-core/zipball/5fc7850ada5e8e03d78c1739c82c64d5e2f7d495", - "reference": "5fc7850ada5e8e03d78c1739c82c64d5e2f7d495", + "url": "https://api.github.com/repos/symfony/security-core/zipball/bbd1a919aec8696a95bf8749d5577fbe74de973c", + "reference": "bbd1a919aec8696a95bf8749d5577fbe74de973c", "shasum": "" }, "require": { @@ -12951,7 +12570,7 @@ "description": "Symfony Security Component - Core Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-core/tree/v6.4.8" + "source": "https://github.com/symfony/security-core/tree/v6.4.13" }, "funding": [ { @@ -12967,20 +12586,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-10-25T15:07:50+00:00" }, { "name": "symfony/security-csrf", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/security-csrf.git", - "reference": "f46ab02b76311087873257071559edcaf6d7ab99" + "reference": "c34421b7d34efbaef5d611ab2e646a0ec464ffe3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-csrf/zipball/f46ab02b76311087873257071559edcaf6d7ab99", - "reference": "f46ab02b76311087873257071559edcaf6d7ab99", + "url": "https://api.github.com/repos/symfony/security-csrf/zipball/c34421b7d34efbaef5d611ab2e646a0ec464ffe3", + "reference": "c34421b7d34efbaef5d611ab2e646a0ec464ffe3", "shasum": "" }, "require": { @@ -13019,7 +12638,7 @@ "description": "Symfony Security Component - CSRF Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-csrf/tree/v6.4.8" + "source": "https://github.com/symfony/security-csrf/tree/v6.4.13" }, "funding": [ { @@ -13035,20 +12654,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/security-http", - "version": "v6.4.8", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/security-http.git", - "reference": "fb82ddec887dc67f3bcf4d6df3cb8efd529be104" + "reference": "cc13b601fc844036fd6c8e7307845da145d5998b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-http/zipball/fb82ddec887dc67f3bcf4d6df3cb8efd529be104", - "reference": "fb82ddec887dc67f3bcf4d6df3cb8efd529be104", + "url": "https://api.github.com/repos/symfony/security-http/zipball/cc13b601fc844036fd6c8e7307845da145d5998b", + "reference": "cc13b601fc844036fd6c8e7307845da145d5998b", "shasum": "" }, "require": { @@ -13107,7 +12726,7 @@ "description": "Symfony Security Component - HTTP Integration", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-http/tree/v6.4.8" + "source": "https://github.com/symfony/security-http/tree/v6.4.14" }, "funding": [ { @@ -13123,20 +12742,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-11-05T15:34:40+00:00" }, { "name": "symfony/serializer", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "d6eda9966a3e5d1823c1cedf41bf98f8ed969d7c" + "reference": "8be421505938b11a0ca4f656e4322232236386f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/d6eda9966a3e5d1823c1cedf41bf98f8ed969d7c", - "reference": "d6eda9966a3e5d1823c1cedf41bf98f8ed969d7c", + "url": "https://api.github.com/repos/symfony/serializer/zipball/8be421505938b11a0ca4f656e4322232236386f0", + "reference": "8be421505938b11a0ca4f656e4322232236386f0", "shasum": "" }, "require": { @@ -13205,7 +12824,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.4.8" + "source": "https://github.com/symfony/serializer/tree/v6.4.13" }, "funding": [ { @@ -13221,7 +12840,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-10-03T09:58:04+00:00" }, { "name": "symfony/service-contracts", @@ -13308,16 +12927,16 @@ }, { "name": "symfony/stimulus-bundle", - "version": "v2.18.1", + "version": "v2.21.0", "source": { "type": "git", "url": "https://github.com/symfony/stimulus-bundle.git", - "reference": "017b60e036c366c8ce0e77864d5aabab436ad73d" + "reference": "e5f7747b514865719e0990389ce35a9b71bebb48" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stimulus-bundle/zipball/017b60e036c366c8ce0e77864d5aabab436ad73d", - "reference": "017b60e036c366c8ce0e77864d5aabab436ad73d", + "url": "https://api.github.com/repos/symfony/stimulus-bundle/zipball/e5f7747b514865719e0990389ce35a9b71bebb48", + "reference": "e5f7747b514865719e0990389ce35a9b71bebb48", "shasum": "" }, "require": { @@ -13357,7 +12976,7 @@ "symfony-ux" ], "support": { - "source": "https://github.com/symfony/stimulus-bundle/tree/v2.18.1" + "source": "https://github.com/symfony/stimulus-bundle/tree/v2.21.0" }, "funding": [ { @@ -13373,20 +12992,20 @@ "type": "tidelift" } ], - "time": "2024-06-11T13:21:54+00:00" + "time": "2024-10-05T22:11:16+00:00" }, { "name": "symfony/stopwatch", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "63e069eb616049632cde9674c46957819454b8aa" + "reference": "2cae0a6f8d04937d02f6d19806251e2104d54f92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/63e069eb616049632cde9674c46957819454b8aa", - "reference": "63e069eb616049632cde9674c46957819454b8aa", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/2cae0a6f8d04937d02f6d19806251e2104d54f92", + "reference": "2cae0a6f8d04937d02f6d19806251e2104d54f92", "shasum": "" }, "require": { @@ -13419,7 +13038,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v6.4.8" + "source": "https://github.com/symfony/stopwatch/tree/v6.4.13" }, "funding": [ { @@ -13435,20 +13054,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/string", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "a147c0f826c4a1f3afb763ab8e009e37c877a44d" + "reference": "38371c60c71c72b3d64d8d76f6b1bb81a2cc3627" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/a147c0f826c4a1f3afb763ab8e009e37c877a44d", - "reference": "a147c0f826c4a1f3afb763ab8e009e37c877a44d", + "url": "https://api.github.com/repos/symfony/string/zipball/38371c60c71c72b3d64d8d76f6b1bb81a2cc3627", + "reference": "38371c60c71c72b3d64d8d76f6b1bb81a2cc3627", "shasum": "" }, "require": { @@ -13505,7 +13124,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.8" + "source": "https://github.com/symfony/string/tree/v6.4.13" }, "funding": [ { @@ -13521,20 +13140,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/translation", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "a002933b13989fc4bd0b58e04bf7eec5210e438a" + "reference": "bee9bfabfa8b4045a66bf82520e492cddbaffa66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/a002933b13989fc4bd0b58e04bf7eec5210e438a", - "reference": "a002933b13989fc4bd0b58e04bf7eec5210e438a", + "url": "https://api.github.com/repos/symfony/translation/zipball/bee9bfabfa8b4045a66bf82520e492cddbaffa66", + "reference": "bee9bfabfa8b4045a66bf82520e492cddbaffa66", "shasum": "" }, "require": { @@ -13600,7 +13219,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.4.8" + "source": "https://github.com/symfony/translation/tree/v6.4.13" }, "funding": [ { @@ -13616,7 +13235,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-27T18:14:25+00:00" }, { "name": "symfony/translation-contracts", @@ -13698,16 +13317,16 @@ }, { "name": "symfony/twig-bridge", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "57de1b7d7499053a2c5beb9344751e8bfd332649" + "reference": "ec3511eef0576f378b2758da9e1c157086babd59" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/57de1b7d7499053a2c5beb9344751e8bfd332649", - "reference": "57de1b7d7499053a2c5beb9344751e8bfd332649", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/ec3511eef0576f378b2758da9e1c157086babd59", + "reference": "ec3511eef0576f378b2758da9e1c157086babd59", "shasum": "" }, "require": { @@ -13787,7 +13406,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v6.4.8" + "source": "https://github.com/symfony/twig-bridge/tree/v6.4.13" }, "funding": [ { @@ -13803,20 +13422,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-10-25T15:07:50+00:00" }, { "name": "symfony/twig-bundle", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "ef17bc8fc2cb2376b235cd1b98f0275a78c5ba65" + "reference": "c3beeb5336aba1ea03c37e526968c2fde3ef25c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/ef17bc8fc2cb2376b235cd1b98f0275a78c5ba65", - "reference": "ef17bc8fc2cb2376b235cd1b98f0275a78c5ba65", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/c3beeb5336aba1ea03c37e526968c2fde3ef25c4", + "reference": "c3beeb5336aba1ea03c37e526968c2fde3ef25c4", "shasum": "" }, "require": { @@ -13871,7 +13490,7 @@ "description": "Provides a tight integration of Twig into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bundle/tree/v6.4.8" + "source": "https://github.com/symfony/twig-bundle/tree/v6.4.13" }, "funding": [ { @@ -13887,20 +13506,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/uid", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "35904eca37a84bb764c560cbfcac9f0ac2bcdbdf" + "reference": "18eb207f0436a993fffbdd811b5b8fa35fa5e007" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/35904eca37a84bb764c560cbfcac9f0ac2bcdbdf", - "reference": "35904eca37a84bb764c560cbfcac9f0ac2bcdbdf", + "url": "https://api.github.com/repos/symfony/uid/zipball/18eb207f0436a993fffbdd811b5b8fa35fa5e007", + "reference": "18eb207f0436a993fffbdd811b5b8fa35fa5e007", "shasum": "" }, "require": { @@ -13945,7 +13564,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v6.4.8" + "source": "https://github.com/symfony/uid/tree/v6.4.13" }, "funding": [ { @@ -13961,20 +13580,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/ux-translator", - "version": "v2.18.1", + "version": "v2.21.0", "source": { "type": "git", "url": "https://github.com/symfony/ux-translator.git", - "reference": "e17e0b5a472b6452bfee17ba38be44c19c03be7a" + "reference": "df523d5f90256a40bc7ceaabfe6664bd9856cb74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ux-translator/zipball/e17e0b5a472b6452bfee17ba38be44c19c03be7a", - "reference": "e17e0b5a472b6452bfee17ba38be44c19c03be7a", + "url": "https://api.github.com/repos/symfony/ux-translator/zipball/df523d5f90256a40bc7ceaabfe6664bd9856cb74", + "reference": "df523d5f90256a40bc7ceaabfe6664bd9856cb74", "shasum": "" }, "require": { @@ -14021,7 +13640,7 @@ "symfony-ux" ], "support": { - "source": "https://github.com/symfony/ux-translator/tree/v2.18.1" + "source": "https://github.com/symfony/ux-translator/tree/v2.21.0" }, "funding": [ { @@ -14037,20 +13656,20 @@ "type": "tidelift" } ], - "time": "2024-06-10T13:33:39+00:00" + "time": "2024-10-12T06:22:44+00:00" }, { "name": "symfony/ux-turbo", - "version": "v2.18.0", + "version": "v2.21.0", "source": { "type": "git", "url": "https://github.com/symfony/ux-turbo.git", - "reference": "e447231ddcc09ab68d29047f47d31a524837dc7a" + "reference": "075c609e54fc421c6b1c1974e46e9a8b2d44277c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ux-turbo/zipball/e447231ddcc09ab68d29047f47d31a524837dc7a", - "reference": "e447231ddcc09ab68d29047f47d31a524837dc7a", + "url": "https://api.github.com/repos/symfony/ux-turbo/zipball/075c609e54fc421c6b1c1974e46e9a8b2d44277c", + "reference": "075c609e54fc421c6b1c1974e46e9a8b2d44277c", "shasum": "" }, "require": { @@ -14065,21 +13684,22 @@ "doctrine/doctrine-bundle": "^2.4.3", "doctrine/orm": "^2.8 | 3.0", "phpstan/phpstan": "^1.10", + "symfony/asset-mapper": "^6.4|^7.0", "symfony/debug-bundle": "^5.4|^6.0|^7.0", "symfony/expression-language": "^5.4|^6.0|^7.0", "symfony/form": "^5.4|^6.0|^7.0", - "symfony/framework-bundle": "^5.4|^6.0|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", "symfony/mercure-bundle": "^0.3.7", "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/panther": "^1.0|^2.0", + "symfony/panther": "^2.1", "symfony/phpunit-bridge": "^5.4|^6.0|^7.0", "symfony/process": "^5.4|6.3.*|^7.0", "symfony/property-access": "^5.4|^6.0|^7.0", "symfony/security-core": "^5.4|^6.0|^7.0", "symfony/stopwatch": "^5.4|^6.0|^7.0", - "symfony/twig-bundle": "^5.4|^6.0|^7.0", - "symfony/web-profiler-bundle": "^5.4|^6.0|^7.0", - "symfony/webpack-encore-bundle": "^2.1.1" + "symfony/twig-bundle": "^6.4|^7.0", + "symfony/ux-twig-component": "^2.21", + "symfony/web-profiler-bundle": "^5.4|^6.0|^7.0" }, "type": "symfony-bundle", "extra": { @@ -14118,7 +13738,7 @@ "turbo-stream" ], "support": { - "source": "https://github.com/symfony/ux-turbo/tree/v2.18.0" + "source": "https://github.com/symfony/ux-turbo/tree/v2.21.0" }, "funding": [ { @@ -14134,20 +13754,20 @@ "type": "tidelift" } ], - "time": "2024-06-01T17:56:14+00:00" + "time": "2024-10-21T19:07:02+00:00" }, { "name": "symfony/validator", - "version": "v6.4.8", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "dab2781371d54c86f6b25623ab16abb2dde2870c" + "reference": "dc259b85e59a6569e205966d447dec0a7d95facf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/dab2781371d54c86f6b25623ab16abb2dde2870c", - "reference": "dab2781371d54c86f6b25623ab16abb2dde2870c", + "url": "https://api.github.com/repos/symfony/validator/zipball/dc259b85e59a6569e205966d447dec0a7d95facf", + "reference": "dc259b85e59a6569e205966d447dec0a7d95facf", "shasum": "" }, "require": { @@ -14215,7 +13835,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v6.4.8" + "source": "https://github.com/symfony/validator/tree/v6.4.14" }, "funding": [ { @@ -14231,20 +13851,20 @@ "type": "tidelift" } ], - "time": "2024-06-02T15:48:50+00:00" + "time": "2024-11-04T11:33:53+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.4.8", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "ad23ca4312395f0a8a8633c831ef4c4ee542ed25" + "reference": "93c09246038178717a9c14b809ea8151ffcf7091" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/ad23ca4312395f0a8a8633c831ef4c4ee542ed25", - "reference": "ad23ca4312395f0a8a8633c831ef4c4ee542ed25", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/93c09246038178717a9c14b809ea8151ffcf7091", + "reference": "93c09246038178717a9c14b809ea8151ffcf7091", "shasum": "" }, "require": { @@ -14300,7 +13920,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.4.8" + "source": "https://github.com/symfony/var-dumper/tree/v6.4.14" }, "funding": [ { @@ -14316,20 +13936,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-11-05T15:34:40+00:00" }, { "name": "symfony/var-exporter", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "792ca836f99b340f2e9ca9497c7953948c49a504" + "reference": "0f605f72a363f8743001038a176eeb2a11223b51" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/792ca836f99b340f2e9ca9497c7953948c49a504", - "reference": "792ca836f99b340f2e9ca9497c7953948c49a504", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/0f605f72a363f8743001038a176eeb2a11223b51", + "reference": "0f605f72a363f8743001038a176eeb2a11223b51", "shasum": "" }, "require": { @@ -14377,7 +13997,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.4.8" + "source": "https://github.com/symfony/var-exporter/tree/v6.4.13" }, "funding": [ { @@ -14393,20 +14013,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/web-link", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/web-link.git", - "reference": "304c67cefe7128ea3957e9bb1ac6ce08a90a635b" + "reference": "4d188b64bb9a9c5e2e4d20c8d5fdce6bbbb32c94" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-link/zipball/304c67cefe7128ea3957e9bb1ac6ce08a90a635b", - "reference": "304c67cefe7128ea3957e9bb1ac6ce08a90a635b", + "url": "https://api.github.com/repos/symfony/web-link/zipball/4d188b64bb9a9c5e2e4d20c8d5fdce6bbbb32c94", + "reference": "4d188b64bb9a9c5e2e4d20c8d5fdce6bbbb32c94", "shasum": "" }, "require": { @@ -14460,7 +14080,7 @@ "push" ], "support": { - "source": "https://github.com/symfony/web-link/tree/v6.4.8" + "source": "https://github.com/symfony/web-link/tree/v6.4.13" }, "funding": [ { @@ -14476,20 +14096,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/webpack-encore-bundle", - "version": "v2.1.1", + "version": "v2.2.0", "source": { "type": "git", "url": "https://github.com/symfony/webpack-encore-bundle.git", - "reference": "75cb918df3f65e28cf0d4bc03042bc45ccb19dd0" + "reference": "e335394b68a775a9b2bd173a8ba4fd2001f3870c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/webpack-encore-bundle/zipball/75cb918df3f65e28cf0d4bc03042bc45ccb19dd0", - "reference": "75cb918df3f65e28cf0d4bc03042bc45ccb19dd0", + "url": "https://api.github.com/repos/symfony/webpack-encore-bundle/zipball/e335394b68a775a9b2bd173a8ba4fd2001f3870c", + "reference": "e335394b68a775a9b2bd173a8ba4fd2001f3870c", "shasum": "" }, "require": { @@ -14502,6 +14122,7 @@ }, "require-dev": { "symfony/framework-bundle": "^5.4 || ^6.2 || ^7.0", + "symfony/http-client": "^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" @@ -14528,10 +14149,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Integration with your Symfony app & Webpack Encore!", + "description": "Integration of your Symfony app with Webpack Encore", "support": { "issues": "https://github.com/symfony/webpack-encore-bundle/issues", - "source": "https://github.com/symfony/webpack-encore-bundle/tree/v2.1.1" + "source": "https://github.com/symfony/webpack-encore-bundle/tree/v2.2.0" }, "funding": [ { @@ -14547,20 +14168,20 @@ "type": "tidelift" } ], - "time": "2023-10-22T18:53:08+00:00" + "time": "2024-10-02T07:27:19+00:00" }, { "name": "symfony/yaml", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "52903de178d542850f6f341ba92995d3d63e60c9" + "reference": "e99b4e94d124b29ee4cf3140e1b537d2dad8cec9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/52903de178d542850f6f341ba92995d3d63e60c9", - "reference": "52903de178d542850f6f341ba92995d3d63e60c9", + "url": "https://api.github.com/repos/symfony/yaml/zipball/e99b4e94d124b29ee4cf3140e1b537d2dad8cec9", + "reference": "e99b4e94d124b29ee4cf3140e1b537d2dad8cec9", "shasum": "" }, "require": { @@ -14603,7 +14224,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.4.8" + "source": "https://github.com/symfony/yaml/tree/v6.4.13" }, "funding": [ { @@ -14619,20 +14240,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "tecnickcom/tc-lib-barcode", - "version": "2.2.1", + "version": "2.3.2", "source": { "type": "git", "url": "https://github.com/tecnickcom/tc-lib-barcode.git", - "reference": "6877202fe7b3f746f22032e22d454c60c3db20fc" + "reference": "e6ba1a00575c949faf8d56cdd7d71c39b3397b92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tecnickcom/tc-lib-barcode/zipball/6877202fe7b3f746f22032e22d454c60c3db20fc", - "reference": "6877202fe7b3f746f22032e22d454c60c3db20fc", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-barcode/zipball/e6ba1a00575c949faf8d56cdd7d71c39b3397b92", + "reference": "e6ba1a00575c949faf8d56cdd7d71c39b3397b92", "shasum": "" }, "require": { @@ -14641,7 +14262,7 @@ "ext-gd": "*", "ext-pcre": "*", "php": ">=8.0", - "tecnickcom/tc-lib-color": "^2.0" + "tecnickcom/tc-lib-color": "^2.2" }, "require-dev": { "pdepend/pdepend": "2.13.0", @@ -14711,7 +14332,7 @@ ], "support": { "issues": "https://github.com/tecnickcom/tc-lib-barcode/issues", - "source": "https://github.com/tecnickcom/tc-lib-barcode/tree/2.2.1" + "source": "https://github.com/tecnickcom/tc-lib-barcode/tree/2.3.2" }, "funding": [ { @@ -14719,20 +14340,20 @@ "type": "custom" } ], - "time": "2024-04-12T04:55:21+00:00" + "time": "2024-10-26T12:35:31+00:00" }, { "name": "tecnickcom/tc-lib-color", - "version": "2.0.8", + "version": "2.2.4", "source": { "type": "git", "url": "https://github.com/tecnickcom/tc-lib-color.git", - "reference": "62a67d7795a3d303e5029dd3ffc7b2d5f8bf614b" + "reference": "37e63eaf78e288569f43640d858808015deacc0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tecnickcom/tc-lib-color/zipball/62a67d7795a3d303e5029dd3ffc7b2d5f8bf614b", - "reference": "62a67d7795a3d303e5029dd3ffc7b2d5f8bf614b", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-color/zipball/37e63eaf78e288569f43640d858808015deacc0a", + "reference": "37e63eaf78e288569f43640d858808015deacc0a", "shasum": "" }, "require": { @@ -14780,7 +14401,7 @@ ], "support": { "issues": "https://github.com/tecnickcom/tc-lib-color/issues", - "source": "https://github.com/tecnickcom/tc-lib-color/tree/2.0.8" + "source": "https://github.com/tecnickcom/tc-lib-color/tree/2.2.4" }, "funding": [ { @@ -14788,7 +14409,7 @@ "type": "custom" } ], - "time": "2024-04-12T04:53:29+00:00" + "time": "2024-10-26T12:33:55+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -14845,23 +14466,23 @@ }, { "name": "twig/cssinliner-extra", - "version": "v3.10.0", + "version": "v3.13.0", "source": { "type": "git", "url": "https://github.com/twigphp/cssinliner-extra.git", - "reference": "10e88e9a887b646c58e3d670383208f15295dd22" + "reference": "cef36c444b1cce4c0978d7aebd20427671a918f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/cssinliner-extra/zipball/10e88e9a887b646c58e3d670383208f15295dd22", - "reference": "10e88e9a887b646c58e3d670383208f15295dd22", + "url": "https://api.github.com/repos/twigphp/cssinliner-extra/zipball/cef36c444b1cce4c0978d7aebd20427671a918f4", + "reference": "cef36c444b1cce4c0978d7aebd20427671a918f4", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.0.2", "symfony/deprecation-contracts": "^2.5|^3", "tijsverkoyen/css-to-inline-styles": "^2.0", - "twig/twig": "^3.0" + "twig/twig": "^3.13|^4.0" }, "require-dev": { "symfony/phpunit-bridge": "^6.4|^7.0" @@ -14898,7 +14519,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/cssinliner-extra/tree/v3.10.0" + "source": "https://github.com/twigphp/cssinliner-extra/tree/v3.13.0" }, "funding": [ { @@ -14910,27 +14531,27 @@ "type": "tidelift" } ], - "time": "2024-05-11T07:35:57+00:00" + "time": "2024-09-03T13:08:40+00:00" }, { "name": "twig/extra-bundle", - "version": "v3.10.0", + "version": "v3.13.0", "source": { "type": "git", "url": "https://github.com/twigphp/twig-extra-bundle.git", - "reference": "cdc6e23aeb7f4953c1039568c3439aab60c56454" + "reference": "21a9a7aa9f79d4493bb6fed4eb2794339f9551f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/cdc6e23aeb7f4953c1039568c3439aab60c56454", - "reference": "cdc6e23aeb7f4953c1039568c3439aab60c56454", + "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/21a9a7aa9f79d4493bb6fed4eb2794339f9551f5", + "reference": "21a9a7aa9f79d4493bb6fed4eb2794339f9551f5", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.0.2", "symfony/framework-bundle": "^5.4|^6.4|^7.0", "symfony/twig-bundle": "^5.4|^6.4|^7.0", - "twig/twig": "^3.0" + "twig/twig": "^3.0|^4.0" }, "require-dev": { "league/commonmark": "^1.0|^2.0", @@ -14972,7 +14593,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.10.0" + "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.13.0" }, "funding": [ { @@ -14984,27 +14605,27 @@ "type": "tidelift" } ], - "time": "2024-05-11T07:35:57+00:00" + "time": "2024-09-01T20:39:12+00:00" }, { "name": "twig/html-extra", - "version": "v3.10.0", + "version": "v3.13.0", "source": { "type": "git", "url": "https://github.com/twigphp/html-extra.git", - "reference": "1c045fc28ace0dcaf464f8e0d4e2aca6347d5fda" + "reference": "8229e750091171c1f11801a525927811c7ac5a7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/html-extra/zipball/1c045fc28ace0dcaf464f8e0d4e2aca6347d5fda", - "reference": "1c045fc28ace0dcaf464f8e0d4e2aca6347d5fda", + "url": "https://api.github.com/repos/twigphp/html-extra/zipball/8229e750091171c1f11801a525927811c7ac5a7e", + "reference": "8229e750091171c1f11801a525927811c7ac5a7e", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.0.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/mime": "^5.4|^6.4|^7.0", - "twig/twig": "^3.0" + "twig/twig": "^3.13|^4.0" }, "require-dev": { "symfony/phpunit-bridge": "^6.4|^7.0" @@ -15040,7 +14661,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/html-extra/tree/v3.10.0" + "source": "https://github.com/twigphp/html-extra/tree/v3.13.0" }, "funding": [ { @@ -15052,27 +14673,27 @@ "type": "tidelift" } ], - "time": "2024-05-11T07:35:57+00:00" + "time": "2024-09-03T13:08:40+00:00" }, { "name": "twig/inky-extra", - "version": "v3.10.0", + "version": "v3.13.0", "source": { "type": "git", "url": "https://github.com/twigphp/inky-extra.git", - "reference": "adfcc3b2becc09e909d30b813cde17351ac82958" + "reference": "60c92c2a435ccd95d7a852229f01098aaf7fbced" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/inky-extra/zipball/adfcc3b2becc09e909d30b813cde17351ac82958", - "reference": "adfcc3b2becc09e909d30b813cde17351ac82958", + "url": "https://api.github.com/repos/twigphp/inky-extra/zipball/60c92c2a435ccd95d7a852229f01098aaf7fbced", + "reference": "60c92c2a435ccd95d7a852229f01098aaf7fbced", "shasum": "" }, "require": { "lorenzo/pinky": "^1.0.5", - "php": ">=7.2.5", + "php": ">=8.0.2", "symfony/deprecation-contracts": "^2.5|^3", - "twig/twig": "^3.0" + "twig/twig": "^3.13|^4.0" }, "require-dev": { "symfony/phpunit-bridge": "^6.4|^7.0" @@ -15110,7 +14731,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/inky-extra/tree/v3.10.0" + "source": "https://github.com/twigphp/inky-extra/tree/v3.13.0" }, "funding": [ { @@ -15122,26 +14743,26 @@ "type": "tidelift" } ], - "time": "2024-05-11T07:35:57+00:00" + "time": "2024-09-03T13:08:40+00:00" }, { "name": "twig/intl-extra", - "version": "v3.10.0", + "version": "v3.13.0", "source": { "type": "git", "url": "https://github.com/twigphp/intl-extra.git", - "reference": "693f6beb8ca91fc6323e01b3addf983812f65c93" + "reference": "1b8d78c5db08bdc61015fd55009d2e84b3aa7e38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/intl-extra/zipball/693f6beb8ca91fc6323e01b3addf983812f65c93", - "reference": "693f6beb8ca91fc6323e01b3addf983812f65c93", + "url": "https://api.github.com/repos/twigphp/intl-extra/zipball/1b8d78c5db08bdc61015fd55009d2e84b3aa7e38", + "reference": "1b8d78c5db08bdc61015fd55009d2e84b3aa7e38", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.0.2", "symfony/intl": "^5.4|^6.4|^7.0", - "twig/twig": "^3.10" + "twig/twig": "^3.13|^4.0" }, "require-dev": { "symfony/phpunit-bridge": "^6.4|^7.0" @@ -15174,7 +14795,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/intl-extra/tree/v3.10.0" + "source": "https://github.com/twigphp/intl-extra/tree/v3.13.0" }, "funding": [ { @@ -15186,26 +14807,26 @@ "type": "tidelift" } ], - "time": "2024-05-11T07:35:57+00:00" + "time": "2024-09-03T13:08:40+00:00" }, { "name": "twig/markdown-extra", - "version": "v3.10.0", + "version": "v3.13.0", "source": { "type": "git", "url": "https://github.com/twigphp/markdown-extra.git", - "reference": "e4bf2419df819dcf9dc7a0b25dd8cd1092cbd86d" + "reference": "25f23c02936f8c7157a8413154c06a462c9c20d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/e4bf2419df819dcf9dc7a0b25dd8cd1092cbd86d", - "reference": "e4bf2419df819dcf9dc7a0b25dd8cd1092cbd86d", + "url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/25f23c02936f8c7157a8413154c06a462c9c20d3", + "reference": "25f23c02936f8c7157a8413154c06a462c9c20d3", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.0.2", "symfony/deprecation-contracts": "^2.5|^3", - "twig/twig": "^3.0" + "twig/twig": "^3.13|^4.0" }, "require-dev": { "erusev/parsedown": "^1.7", @@ -15246,7 +14867,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/markdown-extra/tree/v3.10.0" + "source": "https://github.com/twigphp/markdown-extra/tree/v3.13.0" }, "funding": [ { @@ -15258,27 +14879,27 @@ "type": "tidelift" } ], - "time": "2024-05-11T07:35:57+00:00" + "time": "2024-09-03T20:17:35+00:00" }, { "name": "twig/string-extra", - "version": "v3.10.0", + "version": "v3.13.0", "source": { "type": "git", "url": "https://github.com/twigphp/string-extra.git", - "reference": "cd76ed8ae081bcd4fddf549e92e20c5df76c358a" + "reference": "148e869d87cf4bea9d97896ab49e048e4add3310" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/string-extra/zipball/cd76ed8ae081bcd4fddf549e92e20c5df76c358a", - "reference": "cd76ed8ae081bcd4fddf549e92e20c5df76c358a", + "url": "https://api.github.com/repos/twigphp/string-extra/zipball/148e869d87cf4bea9d97896ab49e048e4add3310", + "reference": "148e869d87cf4bea9d97896ab49e048e4add3310", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.0.2", "symfony/string": "^5.4|^6.4|^7.0", "symfony/translation-contracts": "^1.1|^2|^3", - "twig/twig": "^3.0" + "twig/twig": "^3.13|^4.0" }, "require-dev": { "symfony/phpunit-bridge": "^6.4|^7.0" @@ -15313,7 +14934,7 @@ "unicode" ], "support": { - "source": "https://github.com/twigphp/string-extra/tree/v3.10.0" + "source": "https://github.com/twigphp/string-extra/tree/v3.13.0" }, "funding": [ { @@ -15325,28 +14946,28 @@ "type": "tidelift" } ], - "time": "2024-05-11T07:35:57+00:00" + "time": "2024-09-03T13:08:40+00:00" }, { "name": "twig/twig", - "version": "v3.10.3", + "version": "v3.14.2", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "67f29781ffafa520b0bbfbd8384674b42db04572" + "reference": "0b6f9d8370bb3b7f1ce5313ed8feb0fafd6e399a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/67f29781ffafa520b0bbfbd8384674b42db04572", - "reference": "67f29781ffafa520b0bbfbd8384674b42db04572", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/0b6f9d8370bb3b7f1ce5313ed8feb0fafd6e399a", + "reference": "0b6f9d8370bb3b7f1ce5313ed8feb0fafd6e399a", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.0.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-php80": "^1.22" + "symfony/polyfill-php81": "^1.29" }, "require-dev": { "psr/container": "^1.0|^2.0", @@ -15392,7 +15013,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.10.3" + "source": "https://github.com/twigphp/Twig/tree/v3.14.2" }, "funding": [ { @@ -15404,7 +15025,7 @@ "type": "tidelift" } ], - "time": "2024-05-16T10:04:27+00:00" + "time": "2024-11-07T12:36:22+00:00" }, { "name": "ua-parser/uap-php", @@ -15471,38 +15092,37 @@ }, { "name": "web-auth/cose-lib", - "version": "4.3.0", + "version": "4.4.0", "source": { "type": "git", "url": "https://github.com/web-auth/cose-lib.git", - "reference": "e5c417b3b90e06c84638a18d350e438d760cb955" + "reference": "2166016e48e0214f4f63320a7758a9386d14c92a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/cose-lib/zipball/e5c417b3b90e06c84638a18d350e438d760cb955", - "reference": "e5c417b3b90e06c84638a18d350e438d760cb955", + "url": "https://api.github.com/repos/web-auth/cose-lib/zipball/2166016e48e0214f4f63320a7758a9386d14c92a", + "reference": "2166016e48e0214f4f63320a7758a9386d14c92a", "shasum": "" }, "require": { "brick/math": "^0.9|^0.10|^0.11|^0.12", "ext-json": "*", - "ext-mbstring": "*", "ext-openssl": "*", "php": ">=8.1", "spomky-labs/pki-framework": "^1.0" }, "require-dev": { "ekino/phpstan-banned-code": "^1.0", - "infection/infection": "^0.27", + "infection/infection": "^0.29", "php-parallel-lint/php-parallel-lint": "^1.3", "phpstan/extension-installer": "^1.3", "phpstan/phpstan": "^1.7", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.1", "phpstan/phpstan-strict-rules": "^1.2", - "phpunit/phpunit": "^10.1", - "qossmic/deptrac-shim": "^1.0", - "rector/rector": "^0.19", + "phpunit/phpunit": "^10.1|^11.0", + "qossmic/deptrac": "^2.0", + "rector/rector": "^1.0", "symfony/phpunit-bridge": "^6.4|^7.0", "symplify/easy-coding-standard": "^12.0" }, @@ -15538,7 +15158,7 @@ ], "support": { "issues": "https://github.com/web-auth/cose-lib/issues", - "source": "https://github.com/web-auth/cose-lib/tree/4.3.0" + "source": "https://github.com/web-auth/cose-lib/tree/4.4.0" }, "funding": [ { @@ -15550,129 +15170,49 @@ "type": "patreon" } ], - "time": "2024-02-05T21:00:39+00:00" - }, - { - "name": "web-auth/metadata-service", - "version": "4.8.7", - "source": { - "type": "git", - "url": "https://github.com/web-auth/webauthn-metadata-service.git", - "reference": "64b65c91c0e4e9a299abab3f83f6f4f20c058f0a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-metadata-service/zipball/64b65c91c0e4e9a299abab3f83f6f4f20c058f0a", - "reference": "64b65c91c0e4e9a299abab3f83f6f4f20c058f0a", - "shasum": "" - }, - "require": { - "ext-json": "*", - "lcobucci/clock": "^2.2|^3.0", - "paragonie/constant_time_encoding": "^2.6", - "php": ">=8.1", - "psr/clock": "^1.0", - "psr/event-dispatcher": "^1.0", - "psr/http-client": "^1.0", - "psr/http-factory": "^1.0", - "psr/log": "^1.0|^2.0|^3.0", - "spomky-labs/pki-framework": "^1.0", - "symfony/deprecation-contracts": "^3.2" - }, - "suggest": { - "phpdocumentor/reflection-docblock": "As of 4.5.x, the phpdocumentor/reflection-docblock component will become mandatory for converting objects such as the Metadata Statement", - "psr/clock-implementation": "As of 4.5.x, the PSR Clock implementation will replace lcobucci/clock", - "psr/log-implementation": "Recommended to receive logs from the library", - "symfony/property-access": "As of 4.5.x, the symfony/serializer component will become mandatory for converting objects such as the Metadata Statement", - "symfony/property-info": "As of 4.5.x, the symfony/serializer component will become mandatory for converting objects such as the Metadata Statement", - "symfony/serializer": "As of 4.5.x, the symfony/serializer component will become mandatory for converting objects such as the Metadata Statement", - "web-token/jwt-library": "Mandatory for fetching Metadata Statement from distant sources" - }, - "type": "library", - "extra": { - "thanks": { - "name": "web-auth/webauthn-framework", - "url": "https://github.com/web-auth/webauthn-framework" - } - }, - "autoload": { - "psr-4": { - "Webauthn\\MetadataService\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Florent Morselli", - "homepage": "https://github.com/Spomky" - }, - { - "name": "All contributors", - "homepage": "https://github.com/web-auth/metadata-service/contributors" - } - ], - "description": "Metadata Service for FIDO2/Webauthn", - "homepage": "https://github.com/web-auth", - "keywords": [ - "FIDO2", - "fido", - "webauthn" - ], - "support": { - "source": "https://github.com/web-auth/webauthn-metadata-service/tree/4.8.7" - }, - "funding": [ - { - "url": "https://github.com/Spomky", - "type": "github" - }, - { - "url": "https://www.patreon.com/FlorentMorselli", - "type": "patreon" - } - ], - "time": "2024-06-15T07:18:26+00:00" + "time": "2024-07-18T08:47:32+00:00" }, { "name": "web-auth/webauthn-lib", - "version": "4.8.7", + "version": "4.9.1", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-lib.git", - "reference": "925873eb504a1db8a77dc2b4d2b578334736fa16" + "reference": "fd7a0943c663b325e92ad562c2bcc943e77beeac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/925873eb504a1db8a77dc2b4d2b578334736fa16", - "reference": "925873eb504a1db8a77dc2b4d2b578334736fa16", + "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/fd7a0943c663b325e92ad562c2bcc943e77beeac", + "reference": "fd7a0943c663b325e92ad562c2bcc943e77beeac", "shasum": "" }, "require": { "ext-json": "*", "ext-mbstring": "*", "ext-openssl": "*", - "paragonie/constant_time_encoding": "^2.6", + "lcobucci/clock": "^2.2|^3.0", + "paragonie/constant_time_encoding": "^2.6|^3.0", "php": ">=8.1", + "psr/clock": "^1.0", "psr/event-dispatcher": "^1.0", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", "psr/log": "^1.0|^2.0|^3.0", "spomky-labs/cbor-php": "^3.0", + "spomky-labs/pki-framework": "^1.0", + "symfony/deprecation-contracts": "^3.2", "symfony/uid": "^6.1|^7.0", - "web-auth/cose-lib": "^4.2.3", - "web-auth/metadata-service": "self.version" + "web-auth/cose-lib": "^4.2.3" }, "suggest": { "phpdocumentor/reflection-docblock": "As of 4.5.x, the phpdocumentor/reflection-docblock component will become mandatory for converting objects such as the Metadata Statement", + "psr/clock-implementation": "As of 4.5.x, the PSR Clock implementation will replace lcobucci/clock", "psr/log-implementation": "Recommended to receive logs from the library", "symfony/event-dispatcher": "Recommended to use dispatched events", "symfony/property-access": "As of 4.5.x, the symfony/serializer component will become mandatory for converting objects such as the Metadata Statement", "symfony/property-info": "As of 4.5.x, the symfony/serializer component will become mandatory for converting objects such as the Metadata Statement", "symfony/serializer": "As of 4.5.x, the symfony/serializer component will become mandatory for converting objects such as the Metadata Statement", - "web-token/jwt-library": "Mandatory for the AndroidSafetyNet Attestation Statement support" + "web-token/jwt-library": "Mandatory for fetching Metadata Statement from distant sources" }, "type": "library", "extra": { @@ -15708,7 +15248,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-lib/tree/4.8.7" + "source": "https://github.com/web-auth/webauthn-lib/tree/4.9.1" }, "funding": [ { @@ -15720,20 +15260,20 @@ "type": "patreon" } ], - "time": "2024-04-08T10:04:23+00:00" + "time": "2024-07-16T18:36:36+00:00" }, { "name": "web-auth/webauthn-symfony-bundle", - "version": "4.8.7", + "version": "4.9.1", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-symfony-bundle.git", - "reference": "dc176cce0e57f4264ceb3f91385d571838037c3c" + "reference": "fa6dfa596db1cbf159079c0bb76d746ed84c4a26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-symfony-bundle/zipball/dc176cce0e57f4264ceb3f91385d571838037c3c", - "reference": "dc176cce0e57f4264ceb3f91385d571838037c3c", + "url": "https://api.github.com/repos/web-auth/webauthn-symfony-bundle/zipball/fa6dfa596db1cbf159079c0bb76d746ed84c4a26", + "reference": "fa6dfa596db1cbf159079c0bb76d746ed84c4a26", "shasum": "" }, "require": { @@ -15754,7 +15294,7 @@ "symfony/serializer": "^6.1|^7.0", "symfony/validator": "^6.1|^7.0", "web-auth/webauthn-lib": "self.version", - "web-token/jwt-library": "^3.3" + "web-token/jwt-library": "^3.3|^4.0" }, "type": "symfony-bundle", "extra": { @@ -15793,7 +15333,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-symfony-bundle/tree/4.8.7" + "source": "https://github.com/web-auth/webauthn-symfony-bundle/tree/4.9.1" }, "funding": [ { @@ -15805,28 +15345,28 @@ "type": "patreon" } ], - "time": "2024-04-16T14:41:34+00:00" + "time": "2024-07-11T09:06:25+00:00" }, { "name": "web-token/jwt-library", - "version": "3.4.3", + "version": "3.4.6", "source": { "type": "git", "url": "https://github.com/web-token/jwt-library.git", - "reference": "4b09510eec25c328525048cbdf6042a39a7c28d8" + "reference": "1a25c8ced3e2b3c31d32dcfad215cbd8cb812f28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-token/jwt-library/zipball/4b09510eec25c328525048cbdf6042a39a7c28d8", - "reference": "4b09510eec25c328525048cbdf6042a39a7c28d8", + "url": "https://api.github.com/repos/web-token/jwt-library/zipball/1a25c8ced3e2b3c31d32dcfad215cbd8cb812f28", + "reference": "1a25c8ced3e2b3c31d32dcfad215cbd8cb812f28", "shasum": "" }, "require": { "brick/math": "^0.9|^0.10|^0.11|^0.12", "ext-json": "*", "ext-mbstring": "*", - "paragonie/constant_time_encoding": "^2.6", - "paragonie/sodium_compat": "^1.20", + "paragonie/constant_time_encoding": "^2.6|^3.0", + "paragonie/sodium_compat": "^1.20|^2.0", "php": ">=8.1", "psr/cache": "^3.0", "psr/clock": "^1.0", @@ -15891,7 +15431,7 @@ ], "support": { "issues": "https://github.com/web-token/jwt-library/issues", - "source": "https://github.com/web-token/jwt-library/tree/3.4.3" + "source": "https://github.com/web-token/jwt-library/tree/3.4.6" }, "funding": [ { @@ -15903,7 +15443,7 @@ "type": "patreon" } ], - "time": "2024-04-17T17:41:33+00:00" + "time": "2024-07-02T16:35:11+00:00" }, { "name": "webmozart/assert", @@ -16241,17 +15781,87 @@ "time": "2021-11-02T08:37:34+00:00" }, { - "name": "myclabs/deep-copy", - "version": "1.12.0", + "name": "jbtronics/translation-editor-bundle", + "version": "v1.0", "source": { "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" + "url": "https://github.com/jbtronics/translation-editor-bundle.git", + "reference": "4e771c290bf73dded061808bc3ba3c56bc35aa06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "url": "https://api.github.com/repos/jbtronics/translation-editor-bundle/zipball/4e771c290bf73dded061808bc3ba3c56bc35aa06", + "reference": "4e771c290bf73dded061808bc3ba3c56bc35aa06", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^8.1", + "symfony/deprecation-contracts": "^3.4", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/translation": "^7.0|^6.4", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/twig-bundle": "^7.0|^6.4", + "symfony/web-profiler-bundle": "^7.0|^6.4" + }, + "require-dev": { + "ekino/phpstan-banned-code": "^1.0", + "phpstan/extension-installer": "^1.3", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-strict-rules": "^1.5", + "roave/security-advisories": "dev-latest" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Jbtronics\\TranslationEditorBundle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Böhmer", + "email": "mail@jan-boehmer.de" + } + ], + "description": "A symfony bundle to allow editing translations in the symfony profiler", + "keywords": [ + "profiler", + "symfony", + "symfony-bundle", + "translations" + ], + "support": { + "issues": "https://github.com/jbtronics/translation-editor-bundle/issues", + "source": "https://github.com/jbtronics/translation-editor-bundle/tree/v1.0" + }, + "funding": [ + { + "url": "https://www.paypal.me/do9jhb", + "type": "custom" + }, + { + "url": "https://github.com/jbtronics", + "type": "github" + } + ], + "time": "2024-09-03T16:44:00+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.12.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", "shasum": "" }, "require": { @@ -16290,7 +15900,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" }, "funding": [ { @@ -16298,7 +15908,65 @@ "type": "tidelift" } ], - "time": "2024-06-12T14:39:25+00:00" + "time": "2024-11-08T17:47:46+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.3.1", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" + }, + "time": "2024-10-08T18:51:32+00:00" }, { "name": "phar-io/manifest", @@ -16420,22 +16088,22 @@ }, { "name": "phpstan/extension-installer", - "version": "1.4.1", + "version": "1.4.3", "source": { "type": "git", "url": "https://github.com/phpstan/extension-installer.git", - "reference": "f6b87faf9fc7978eab2f7919a8760bc9f58f9203" + "reference": "85e90b3942d06b2326fba0403ec24fe912372936" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/f6b87faf9fc7978eab2f7919a8760bc9f58f9203", - "reference": "f6b87faf9fc7978eab2f7919a8760bc9f58f9203", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/85e90b3942d06b2326fba0403ec24fe912372936", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936", "shasum": "" }, "require": { "composer-plugin-api": "^2.0", "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.9.0" + "phpstan/phpstan": "^1.9.0 || ^2.0" }, "require-dev": { "composer/composer": "^2.0", @@ -16456,24 +16124,28 @@ "MIT" ], "description": "Composer plugin for automatic installation of PHPStan extensions", + "keywords": [ + "dev", + "static analysis" + ], "support": { "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.4.1" + "source": "https://github.com/phpstan/extension-installer/tree/1.4.3" }, - "time": "2024-06-10T08:20:49+00:00" + "time": "2024-09-04T20:21:43+00:00" }, { "name": "phpstan/phpstan", - "version": "1.11.5", + "version": "1.12.8", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "490f0ae1c92b082f154681d7849aee776a7c1443" + "reference": "f6a60a4d66142b8156c9da923f1972657bc4748c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/490f0ae1c92b082f154681d7849aee776a7c1443", - "reference": "490f0ae1c92b082f154681d7849aee776a7c1443", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f6a60a4d66142b8156c9da923f1972657bc4748c", + "reference": "f6a60a4d66142b8156c9da923f1972657bc4748c", "shasum": "" }, "require": { @@ -16518,25 +16190,25 @@ "type": "github" } ], - "time": "2024-06-17T15:10:54+00:00" + "time": "2024-11-06T19:06:49+00:00" }, { "name": "phpstan/phpstan-doctrine", - "version": "1.4.3", + "version": "1.5.5", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-doctrine.git", - "reference": "dd27a3e83777ba0d9e9cedfaf4ebf95ff67b271f" + "reference": "4e9c77fc7bc3293f773fb2d8155c99572a3c89a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/dd27a3e83777ba0d9e9cedfaf4ebf95ff67b271f", - "reference": "dd27a3e83777ba0d9e9cedfaf4ebf95ff67b271f", + "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/4e9c77fc7bc3293f773fb2d8155c99572a3c89a4", + "reference": "4e9c77fc7bc3293f773fb2d8155c99572a3c89a4", "shasum": "" }, "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.11" + "phpstan/phpstan": "^1.12.6" }, "conflict": { "doctrine/collections": "<1.0", @@ -16563,7 +16235,7 @@ "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/phpstan-phpunit": "^1.3.13", "phpstan/phpstan-strict-rules": "^1.5.1", - "phpunit/phpunit": "^9.6.16", + "phpunit/phpunit": "^9.6.20", "ramsey/uuid": "^4.2", "symfony/cache": "^5.4" }, @@ -16588,27 +16260,27 @@ "description": "Doctrine extensions for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-doctrine/issues", - "source": "https://github.com/phpstan/phpstan-doctrine/tree/1.4.3" + "source": "https://github.com/phpstan/phpstan-doctrine/tree/1.5.5" }, - "time": "2024-06-08T05:48:50+00:00" + "time": "2024-10-29T12:19:49+00:00" }, { "name": "phpstan/phpstan-strict-rules", - "version": "1.6.0", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "363f921dd8441777d4fc137deb99beb486c77df1" + "reference": "daeec748b53de80a97498462513066834ec28f8b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/363f921dd8441777d4fc137deb99beb486c77df1", - "reference": "363f921dd8441777d4fc137deb99beb486c77df1", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/daeec748b53de80a97498462513066834ec28f8b", + "reference": "daeec748b53de80a97498462513066834ec28f8b", "shasum": "" }, "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.11" + "phpstan/phpstan": "^1.12.4" }, "require-dev": { "nikic/php-parser": "^4.13.0", @@ -16637,28 +16309,28 @@ "description": "Extra strict and opinionated rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.6.0" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.6.1" }, - "time": "2024-04-20T06:37:51+00:00" + "time": "2024-09-20T14:04:44+00:00" }, { "name": "phpstan/phpstan-symfony", - "version": "1.4.4", + "version": "1.4.12", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "bca27f1701fc1a297749e6c2a1e3da4462c1a6af" + "reference": "c7b7e7f520893621558bfbfdb2694d4364565c1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/bca27f1701fc1a297749e6c2a1e3da4462c1a6af", - "reference": "bca27f1701fc1a297749e6c2a1e3da4462c1a6af", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/c7b7e7f520893621558bfbfdb2694d4364565c1d", + "reference": "c7b7e7f520893621558bfbfdb2694d4364565c1d", "shasum": "" }, "require": { "ext-simplexml": "*", "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.11" + "phpstan/phpstan": "^1.12" }, "conflict": { "symfony/framework-bundle": "<3.0" @@ -16709,41 +16381,41 @@ "description": "Symfony Framework extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-symfony/issues", - "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.4" + "source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.12" }, - "time": "2024-06-07T09:43:24+00:00" + "time": "2024-11-06T10:13:18+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.31", + "version": "9.2.32", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965" + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/48c34b5d8d983006bd2adc2d0de92963b9155965", - "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.18 || ^5.0", + "nikic/php-parser": "^4.19.1 || ^5.1.0", "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" + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-text-template": "^2.0.4", + "sebastian/code-unit-reverse-lookup": "^2.0.3", + "sebastian/complexity": "^2.0.3", + "sebastian/environment": "^5.1.5", + "sebastian/lines-of-code": "^1.0.4", + "sebastian/version": "^3.0.2", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.6" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -16752,7 +16424,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.2-dev" + "dev-main": "9.2.x-dev" } }, "autoload": { @@ -16781,7 +16453,7 @@ "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.31" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" }, "funding": [ { @@ -16789,7 +16461,7 @@ "type": "github" } ], - "time": "2024-03-02T06:37:42+00:00" + "time": "2024-08-22T04:23:01+00:00" }, { "name": "phpunit/php-file-iterator", @@ -17034,45 +16706,45 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.19", + "version": "9.6.21", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8" + "reference": "de6abf3b6f8dd955fac3caad3af7a9504e8c2ffa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1a54a473501ef4cdeaae4e06891674114d79db8", - "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/de6abf3b6f8dd955fac3caad3af7a9504e8c2ffa", + "reference": "de6abf3b6f8dd955fac3caad3af7a9504e8c2ffa", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1 || ^2", + "doctrine/instantiator": "^1.5.0 || ^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", + "myclabs/deep-copy": "^1.12.0", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.28", - "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-code-coverage": "^9.2.32", + "phpunit/php-file-iterator": "^3.0.6", "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", + "phpunit/php-text-template": "^2.0.4", + "phpunit/php-timer": "^5.0.3", + "sebastian/cli-parser": "^1.0.2", + "sebastian/code-unit": "^1.0.8", "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/diff": "^4.0.6", + "sebastian/environment": "^5.1.5", + "sebastian/exporter": "^4.0.6", + "sebastian/global-state": "^5.0.7", + "sebastian/object-enumerator": "^4.0.4", + "sebastian/resource-operations": "^3.0.4", + "sebastian/type": "^3.2.1", "sebastian/version": "^3.0.2" }, "suggest": { @@ -17117,7 +16789,7 @@ "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.19" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.21" }, "funding": [ { @@ -17133,25 +16805,25 @@ "type": "tidelift" } ], - "time": "2024-04-05T04:35:58+00:00" + "time": "2024-09-19T10:50:18+00:00" }, { "name": "rector/rector", - "version": "0.18.13", + "version": "1.2.10", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "f8011a76d36aa4f839f60f3b4f97707d97176618" + "reference": "40f9cf38c05296bd32f444121336a521a293fa61" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/f8011a76d36aa4f839f60f3b4f97707d97176618", - "reference": "f8011a76d36aa4f839f60f3b4f97707d97176618", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/40f9cf38c05296bd32f444121336a521a293fa61", + "reference": "40f9cf38c05296bd32f444121336a521a293fa61", "shasum": "" }, "require": { "php": "^7.2|^8.0", - "phpstan/phpstan": "^1.10.35" + "phpstan/phpstan": "^1.12.5" }, "conflict": { "rector/rector-doctrine": "*", @@ -17159,6 +16831,9 @@ "rector/rector-phpunit": "*", "rector/rector-symfony": "*" }, + "suggest": { + "ext-dom": "To manipulate phpunit.xml via the custom-rule command" + }, "bin": [ "bin/rector" ], @@ -17181,7 +16856,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/0.18.13" + "source": "https://github.com/rectorphp/rector/tree/1.2.10" }, "funding": [ { @@ -17189,7 +16864,7 @@ "type": "github" } ], - "time": "2023-12-20T16:08:01+00:00" + "time": "2024-11-08T13:59:10+00:00" }, { "name": "roave/security-advisories", @@ -17197,21 +16872,24 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "a36c08c63614a0da558615424f8b37b3216416b6" + "reference": "e63317470a1b96346be224a68f9e64567e1001c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/a36c08c63614a0da558615424f8b37b3216416b6", - "reference": "a36c08c63614a0da558615424f8b37b3216416b6", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/e63317470a1b96346be224a68f9e64567e1001c3", + "reference": "e63317470a1b96346be224a68f9e64567e1001c3", "shasum": "" }, "conflict": { "3f/pygmentize": "<1.2", - "admidio/admidio": "<4.2.13", + "admidio/admidio": "<4.3.12", "adodb/adodb-php": "<=5.20.20|>=5.21,<=5.21.3", "aheinze/cockpit": "<2.2", - "aimeos/ai-client-html": ">=2020.04.1,<2020.10.27|>=2021.04.1,<2021.10.21|>=2022.04.1,<2022.10.12|>=2023.04.1,<2023.10.14|>=2024.04.1,<2024.04.4", - "aimeos/aimeos-core": "<2024.04.7", + "aimeos/ai-admin-graphql": ">=2022.04.1,<2022.10.10|>=2023.04.1,<2023.10.6|>=2024.04.1,<2024.07.2", + "aimeos/ai-admin-jsonadm": "<2020.10.13|>=2021.04.1,<2021.10.6|>=2022.04.1,<2022.10.3|>=2023.04.1,<2023.10.4|==2024.04.1", + "aimeos/ai-client-html": ">=2020.04.1,<2020.10.27|>=2021.04.1,<2021.10.22|>=2022.04.1,<2022.10.13|>=2023.04.1,<2023.10.15|>=2024.04.1,<2024.04.7", + "aimeos/ai-controller-frontend": "<2020.10.15|>=2021.04.1,<2021.10.8|>=2022.04.1,<2022.10.8|>=2023.04.1,<2023.10.9|==2024.04.1", + "aimeos/aimeos-core": ">=2022.04.1,<2022.10.17|>=2023.04.1,<2023.10.17|>=2024.04.1,<2024.04.7", "aimeos/aimeos-typo3": "<19.10.12|>=20,<20.10.5", "airesvsg/acf-to-rest-api": "<=3.1", "akaunting/akaunting": "<2.1.13", @@ -17219,6 +16897,7 @@ "alextselegidis/easyappointments": "<1.5", "alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1", "amazing/media2click": ">=1,<1.3.3", + "ameos/ameos_tarteaucitron": "<1.2.23", "amphp/artax": "<1.0.6|>=2,<2.0.6", "amphp/http": "<=1.7.2|>=2,<=2.1", "amphp/http-client": ">=4,<4.4", @@ -17236,12 +16915,13 @@ "athlon1600/php-proxy": "<=5.1", "athlon1600/php-proxy-app": "<=3", "austintoddj/canvas": "<=3.4.2", - "automad/automad": "<=1.10.9", + "auth0/wordpress": "<=4.6", + "automad/automad": "<2.0.0.0-alpha5", "automattic/jetpack": "<9.8", "awesome-support/awesome-support": "<=6.0.7", "aws/aws-sdk-php": "<3.288.1", "azuracast/azuracast": "<0.18.3", - "backdrop/backdrop": "<1.24.2", + "backdrop/backdrop": "<1.27.3|>=1.28,<1.28.2", "backpack/crud": "<3.4.9", "bacula-web/bacula-web": "<8.0.0.0-RC2-dev", "badaso/core": "<2.7", @@ -17250,13 +16930,13 @@ "barrelstrength/sprout-forms": "<3.9", "barryvdh/laravel-translation-manager": "<0.6.2", "barzahlen/barzahlen-php": "<2.0.1", - "baserproject/basercms": "<5.0.9", + "baserproject/basercms": "<=5.1.1", "bassjobsen/bootstrap-3-typeahead": ">4.0.2", "bbpress/bbpress": "<2.6.5", "bcosca/fatfree": "<3.7.2", "bedita/bedita": "<4", "bigfork/silverstripe-form-capture": ">=3,<3.1.1", - "billz/raspap-webgui": "<2.9.5", + "billz/raspap-webgui": "<=3.1.4", "bk2k/bootstrap-package": ">=7.1,<7.1.2|>=8,<8.0.8|>=9,<9.0.4|>=9.1,<9.1.3|>=10,<10.0.10|>=11,<11.0.3", "blueimp/jquery-file-upload": "==6.4.4", "bmarshall511/wordpress_zero_spam": "<5.2.13", @@ -17295,31 +16975,34 @@ "codeigniter4/shield": "<1.0.0.0-beta8", "codiad/codiad": "<=2.8.4", "composer/composer": "<1.10.27|>=2,<2.2.24|>=2.3,<2.7.7", - "concrete5/concrete5": "<9.2.8", + "concrete5/concrete5": "<9.3.4", "concrete5/core": "<8.5.8|>=9,<9.1", "contao-components/mediaelement": ">=2.14.2,<2.21.1", "contao/comments-bundle": ">=2,<4.13.40|>=5.0.0.0-RC1-dev,<5.3.4", - "contao/contao": ">=3,<3.5.37|>=4,<4.4.56|>=4.5,<4.9.40|>=4.10,<4.11.7|>=4.13,<4.13.21|>=5.1,<5.1.4", + "contao/contao": "<=5.4.1", "contao/core": "<3.5.39", - "contao/core-bundle": "<4.13.40|>=5,<5.3.4", + "contao/core-bundle": "<4.13.49|>=5,<5.3.15|>=5.4,<5.4.3", "contao/listing-bundle": ">=3,<=3.5.30|>=4,<4.4.8", "contao/managed-edition": "<=1.5", "corveda/phpsandbox": "<1.3.5", "cosenary/instagram": "<=2.3", - "craftcms/cms": "<4.6.2", + "craftcms/cms": "<4.6.2|>=5,<=5.2.2", "croogo/croogo": "<4", "cuyz/valinor": "<0.12", + "czim/file-handling": "<1.5|>=2,<2.3", "czproject/git-php": "<4.0.3", + "damienharper/auditor-bundle": "<5.2.6", "dapphp/securimage": "<3.6.6", "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", + "dcat/laravel-admin": "<=2.1.3", "derhansen/fe_change_pwd": "<2.0.5|>=3,<3.0.3", "derhansen/sf_event_mgt": "<4.3.1|>=5,<5.1.1|>=7,<7.4", "desperado/xml-bundle": "<=0.1.7", + "dev-lancer/minecraft-motd-parser": "<=1.0.5", "devgroup/dotplant": "<2020.09.14-dev", "directmailteam/direct-mail": "<6.0.3|>=7,<7.0.3|>=8,<9.5.2", "doctrine/annotations": "<1.2.7", @@ -17334,14 +17017,15 @@ "dolibarr/dolibarr": "<19.0.2", "dompdf/dompdf": "<2.0.4", "doublethreedigital/guest-entries": "<3.1.2", - "drupal/core": ">=6,<6.38|>=7,<7.96|>=8,<10.1.8|>=10.2,<10.2.2", - "drupal/drupal": ">=5,<5.11|>=6,<6.38|>=7,<7.80|>=8,<8.9.16|>=9,<9.1.12|>=9.2,<9.2.4", + "drupal/core": ">=6,<6.38|>=7,<7.96|>=8,<10.2.9|>=10.3,<10.3.6|>=11,<11.0.5", + "drupal/core-recommended": ">=8,<10.2.9|>=10.3,<10.3.6|>=11,<11.0.5", + "drupal/drupal": ">=5,<5.11|>=6,<6.38|>=7,<7.80|>=8,<10.2.9|>=10.3,<10.3.6|>=11,<11.0.5", "duncanmcclean/guest-entries": "<3.1.2", "dweeves/magmi": "<=0.7.24", "ec-cube/ec-cube": "<2.4.4|>=2.11,<=2.17.1|>=3,<=3.0.18.0-patch4|>=4,<=4.1.2", "ecodev/newsletter": "<=4", "ectouch/ectouch": "<=2.7.2", - "egroupware/egroupware": "<16.1.20170922", + "egroupware/egroupware": "<23.1.20240624", "elefant/cms": "<2.0.7", "elgg/elgg": "<3.3.24|>=4,<4.0.5", "elijaa/phpmemcacheadmin": "<=1.3", @@ -17359,29 +17043,33 @@ "ezsystems/ezdemo-ls-extension": ">=5.4,<5.4.2.1-dev", "ezsystems/ezfind-ls": ">=5.3,<5.3.6.1-dev|>=5.4,<5.4.11.1-dev|>=2017.12,<2017.12.0.1-dev", "ezsystems/ezplatform": "<=1.13.6|>=2,<=2.5.24", - "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6|>=1.5,<1.5.29|>=2.3,<2.3.26", + "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6|>=1.5,<1.5.29|>=2.3,<2.3.26|>=3.3,<3.3.39", "ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2.1|>=5,<5.0.1|>=5.1,<5.1.1", "ezsystems/ezplatform-graphql": ">=1.0.0.0-RC1-dev,<1.0.13|>=2.0.0.0-beta1,<2.3.12", "ezsystems/ezplatform-kernel": "<1.2.5.1-dev|>=1.3,<1.3.35", "ezsystems/ezplatform-rest": ">=1.2,<=1.2.2|>=1.3,<1.3.8", - "ezsystems/ezplatform-richtext": ">=2.3,<2.3.7.1-dev", + "ezsystems/ezplatform-richtext": ">=2.3,<2.3.7.1-dev|>=3.3,<3.3.40", "ezsystems/ezplatform-solr-search-engine": ">=1.7,<1.7.12|>=2,<2.0.2|>=3.3,<3.3.15", "ezsystems/ezplatform-user": ">=1,<1.0.1", "ezsystems/ezpublish-kernel": "<6.13.8.2-dev|>=7,<7.5.31", "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", + "ezyang/htmlpurifier": "<=4.2", "facade/ignition": "<1.16.15|>=2,<2.4.2|>=2.5,<2.5.2", "facturascripts/facturascripts": "<=2022.08", "fastly/magento2": "<1.2.26", "feehi/cms": "<=2.1.1", "feehi/feehicms": "<=2.1.1", "fenom/fenom": "<=2.12.1", + "filament/actions": ">=3.2,<3.2.123", + "filament/infolists": ">=3,<3.2.115", + "filament/tables": ">=3,<3.2.115", "filegator/filegator": "<7.8", "filp/whoops": "<2.1.13", "fineuploader/php-traditional-server": "<=1.2.2", "firebase/php-jwt": "<6", + "fisharebest/webtrees": "<=2.1.18", "fixpunkt/fp-masterquiz": "<2.2.1|>=3,<3.5.2", "fixpunkt/fp-newsletter": "<1.1.1|>=2,<2.1.2|>=2.2,<3.2.6", "flarum/core": "<1.8.5", @@ -17404,19 +17092,19 @@ "friendsofsymfony/rest-bundle": ">=1.2,<1.2.2", "friendsofsymfony/user-bundle": ">=1,<1.3.5", "friendsofsymfony1/swiftmailer": ">=4,<5.4.13|>=6,<6.2.5", - "friendsofsymfony1/symfony1": ">=1.1,<1.15.19", + "friendsofsymfony1/symfony1": ">=1.1,<1.5.19", "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|>=4.0.1,<=4.1.3", - "froxlor/froxlor": "<2.1.9", + "froxlor/froxlor": "<=2.2.0.0-RC3", "frozennode/administrator": "<=5.0.12", "fuel/core": "<1.8.1", - "funadmin/funadmin": "<=3.2|>=3.3.2,<=3.3.3", + "funadmin/funadmin": "<=5.0.2", "gaoming13/wechat-php-sdk": "<=1.10.2", "genix/cms": "<=1.1.11", "getformwork/formwork": "<1.13.1|==2.0.0.0-beta1", "getgrav/grav": "<1.7.46", - "getkirby/cms": "<4.1.1", + "getkirby/cms": "<=3.6.6.5|>=3.7,<=3.7.5.4|>=3.8,<=3.8.4.3|>=3.9,<=3.9.8.1|>=3.10,<=3.10.1|>=4,<=4.3", "getkirby/kirby": "<=2.5.12", "getkirby/panel": "<2.5.14", "getkirby/starterkit": "<=3.7.0.2", @@ -17442,8 +17130,9 @@ "hov/jobfair": "<1.0.13|>=2,<2.0.2", "httpsoft/http-message": "<1.0.12", "hyn/multi-tenant": ">=5.6,<5.7.2", - "ibexa/admin-ui": ">=4.2,<4.2.3", + "ibexa/admin-ui": ">=4.2,<4.2.3|>=4.6.0.0-beta1,<4.6.9", "ibexa/core": ">=4,<4.0.7|>=4.1,<4.1.4|>=4.2,<4.2.3|>=4.5,<4.5.6|>=4.6,<4.6.2", + "ibexa/fieldtype-richtext": ">=4.6,<4.6.10", "ibexa/graphql": ">=2.5,<2.5.31|>=3.3,<3.3.28|>=4.2,<4.2.3", "ibexa/post-install": "<=1.0.4", "ibexa/solr": ">=4.5,<4.5.4", @@ -17462,9 +17151,11 @@ "in2code/femanager": "<5.5.3|>=6,<6.3.4|>=7,<7.2.3", "in2code/ipandlanguageredirect": "<5.1.2", "in2code/lux": "<17.6.1|>=18,<24.0.2", + "in2code/powermail": "<7.5.1|>=8,<8.5.1|>=9,<10.9.1|>=11,<12.4.1", "innologi/typo3-appointments": "<2.0.6", "intelliants/subrion": "<4.2.2", "inter-mediator/inter-mediator": "==5.5", + "ipl/web": "<0.10.1", "islandora/islandora": ">=2,<2.4.1", "ivankristianto/phpwhois": "<=4.3", "jackalope/jackalope-doctrine-dbal": "<1.7.4", @@ -17485,25 +17176,29 @@ "jsdecena/laracom": "<2.0.9", "jsmitty12/phpwhois": "<5.1", "juzaweb/cms": "<=3.4", + "jweiland/events2": "<8.3.8|>=9,<9.0.6", "kazist/phpwhois": "<=4.2.6", "kelvinmo/simplexrd": "<3.1.1", "kevinpapst/kimai2": "<1.16.7", "khodakhah/nodcms": "<=3", - "kimai/kimai": "<2.16", + "kimai/kimai": "<=2.20.1", "kitodo/presentation": "<3.2.3|>=3.3,<3.3.4", "klaviyo/magento2-extension": ">=1,<3", "knplabs/knp-snappy": "<=1.4.2", "kohana/core": "<3.3.3", - "krayin/laravel-crm": "<1.2.2", + "krayin/laravel-crm": "<=1.3", "kreait/firebase-php": ">=3.2,<3.8.1", "kumbiaphp/kumbiapp": "<=1.1.1", "la-haute-societe/tcpdf": "<6.2.22", "laminas/laminas-diactoros": "<2.18.1|==2.19|==2.20|==2.21|==2.22|==2.23|>=2.24,<2.24.2|>=2.25,<2.25.2", "laminas/laminas-form": "<2.17.1|>=3,<3.0.2|>=3.1,<3.1.1", "laminas/laminas-http": "<2.14.2", + "lara-zeus/artemis": ">=1,<=1.0.6", + "lara-zeus/dynamic-dashboard": ">=3,<=3.0.1", "laravel/fortify": "<1.11.1", "laravel/framework": "<6.20.44|>=7,<7.30.6|>=8,<8.75", "laravel/laravel": ">=5.4,<5.4.22", + "laravel/reverb": "<1.4", "laravel/socialite": ">=1,<2.0.10", "latte/latte": "<2.10.8", "lavalite/cms": "<=9|==10.1", @@ -17516,26 +17211,30 @@ "librenms/librenms": "<2017.08.18", "liftkit/database": "<2.13.2", "lightsaml/lightsaml": "<1.3.5", - "limesurvey/limesurvey": "<3.27.19", + "limesurvey/limesurvey": "<6.5.12", "livehelperchat/livehelperchat": "<=3.91", - "livewire/livewire": ">2.2.4,<2.2.6|>=3.3.5,<3.4.9", + "livewire/livewire": "<2.12.7|>=3.0.0.0-beta1,<3.5.2", "lms/routes": "<2.1.1", "localizationteam/l10nmgr": "<7.4|>=8,<8.7|>=9,<9.2", "luyadev/yii-helpers": "<1.2.1", - "magento/community-edition": "<2.4.5|==2.4.5|>=2.4.5.0-patch1,<2.4.5.0-patch8|==2.4.6|>=2.4.6.0-patch1,<2.4.6.0-patch6|==2.4.7", + "maestroerror/php-heic-to-jpg": "<1.0.5", + "magento/community-edition": "<2.4.5|==2.4.5|>=2.4.5.0-patch1,<2.4.5.0-patch10|==2.4.6|>=2.4.6.0-patch1,<2.4.6.0-patch8|>=2.4.7.0-beta1,<2.4.7.0-patch3", "magento/core": "<=1.9.4.5", "magento/magento1ce": "<1.9.4.3-dev", "magento/magento1ee": ">=1,<1.14.4.3-dev", - "magento/product-community-edition": ">=2,<2.2.10|>=2.3,<2.3.2.0-patch2", + "magento/product-community-edition": "<2.4.4.0-patch9|>=2.4.5,<2.4.5.0-patch8|>=2.4.6,<2.4.6.0-patch6|>=2.4.7,<2.4.7.0-patch1", "magneto/core": "<1.9.4.4-dev", "maikuolan/phpmussel": ">=1,<1.6", "mainwp/mainwp": "<=4.4.3.3", - "mantisbt/mantisbt": "<2.26.2", + "mantisbt/mantisbt": "<=2.26.3", "marcwillmann/turn": "<0.3.3", "matyhtf/framework": "<3.0.6", - "mautic/core": "<4.4.12|>=5.0.0.0-alpha,<5.0.4", + "mautic/core": "<4.4.13|>=5,<5.1.1", + "mautic/core-lib": ">=1.0.0.0-beta,<4.4.13|>=5.0.0.0-alpha,<5.1.1", + "maximebf/debugbar": "<1.19", "mdanter/ecc": "<2", - "mediawiki/core": "<1.36.2", + "mediawiki/cargo": "<3.6.1", + "mediawiki/core": "<1.39.5|==1.40", "mediawiki/matomo": "<2.4.3", "mediawiki/semantic-media-wiki": "<4.0.2", "melisplatform/melis-asset-manager": "<5.0.1", @@ -17546,7 +17245,7 @@ "microsoft/microsoft-graph": ">=1.16,<1.109.1|>=2,<2.0.1", "microsoft/microsoft-graph-beta": "<2.0.1", "microsoft/microsoft-graph-core": "<2.0.2", - "microweber/microweber": "<=2.0.4", + "microweber/microweber": "<=2.0.16", "mikehaertl/php-shellcommand": "<1.6.1", "miniorange/miniorange-saml": "<1.4.3", "mittwald/typo3_forum": "<1.2.1", @@ -17555,7 +17254,7 @@ "mojo42/jirafeau": "<4.4", "mongodb/mongodb": ">=1,<1.9.2", "monolog/monolog": ">=1.8,<1.12", - "moodle/moodle": "<4.3.4", + "moodle/moodle": "<4.3.6|>=4.4.0.0-beta,<4.4.2", "mos/cimage": "<0.7.19", "movim/moxl": ">=0.8,<=0.10", "movingbytes/social-network": "<=1.2.1", @@ -17568,6 +17267,7 @@ "munkireport/softwareupdate": "<1.6", "mustache/mustache": ">=2,<2.14.1", "namshi/jose": "<2.2", + "nategood/httpful": "<1", "neoan3-apps/template": "<1.1.1", "neorazorx/facturascripts": "<2022.04", "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", @@ -17590,16 +17290,16 @@ "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/october": "<=3.4.4", + "october/october": "<=3.6.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.5.2", + "october/system": "<1.0.476|>=1.1,<1.1.12|>=2,<2.2.34|>=3,<3.5.15", "omeka/omeka-s": "<4.0.3", "onelogin/php-saml": "<2.10.4", "oneup/uploader-bundle": ">=1,<1.9.3|>=2,<2.1.5", "open-web-analytics/open-web-analytics": "<1.7.4", - "opencart/opencart": "<=3.0.3.7|>=4,<4.0.2.3-dev", + "opencart/opencart": ">=0", "openid/php-openid": "<2.3", - "openmage/magento-lts": "<20.5", + "openmage/magento-lts": "<20.10.1", "opensolutions/vimbadmin": "<=3.0.15", "opensource-workshop/connect-cms": "<1.7.2|>=2,<2.3.2", "orchid/platform": ">=9,<9.4.4|>=14.0.0.0-alpha4,<14.5", @@ -17609,6 +17309,7 @@ "oro/crm-call-bundle": ">=4.2,<=4.2.5|>=5,<5.0.4|>=5.1,<5.1.1", "oro/customer-portal": ">=4.1,<=4.1.13|>=4.2,<=4.2.10|>=5,<=5.0.11|>=5.1,<=5.1.3", "oro/platform": ">=1.7,<1.7.4|>=3.1,<3.1.29|>=4.1,<4.1.17|>=4.2,<=4.2.10|>=5,<=5.0.12|>=5.1,<=5.1.3", + "oveleon/contao-cookiebar": "<1.16.3|>=2,<2.1.3", "oxid-esales/oxideshop-ce": "<4.5", "oxid-esales/paymorrow-module": ">=1,<1.0.2|>=2,<2.0.1", "packbackbooks/lti-1-3-php-library": "<5", @@ -17632,7 +17333,7 @@ "phenx/php-svg-lib": "<0.5.2", "php-censor/php-censor": "<2.0.13|>=2.1,<2.1.5", "php-mod/curl": "<2.3.2", - "phpbb/phpbb": "<3.2.10|>=3.3,<3.3.1", + "phpbb/phpbb": "<3.3.11", "phpems/phpems": ">=6,<=6.1.3", "phpfastcache/phpfastcache": "<6.1.5|>=7,<7.1.2|>=8,<8.0.7", "phpmailer/phpmailer": "<6.5", @@ -17640,8 +17341,8 @@ "phpmyadmin/phpmyadmin": "<5.2.1", "phpmyfaq/phpmyfaq": "<3.2.5|==3.2.5", "phpoffice/common": "<0.2.9", - "phpoffice/phpexcel": "<1.8", - "phpoffice/phpspreadsheet": "<1.16", + "phpoffice/phpexcel": "<1.8.1", + "phpoffice/phpspreadsheet": "<1.29.2|>=2,<2.1.1|>=2.2,<2.3", "phpseclib/phpseclib": "<2.0.47|>=3,<3.0.36", "phpservermon/phpservermon": "<3.6", "phpsysinfo/phpsysinfo": "<3.4.3", @@ -17650,9 +17351,10 @@ "phpxmlrpc/extras": "<0.6.1", "phpxmlrpc/phpxmlrpc": "<4.9.2", "pi/pi": "<=2.5", - "pimcore/admin-ui-classic-bundle": "<=1.4.2", + "pimcore/admin-ui-classic-bundle": "<1.5.4", "pimcore/customer-management-framework-bundle": "<4.0.6", "pimcore/data-hub": "<1.2.4", + "pimcore/data-importer": "<1.8.9|>=1.9,<1.9.3", "pimcore/demo": "<10.3", "pimcore/ecommerce-framework-bundle": "<1.0.10", "pimcore/perspective-editor": "<1.5.1", @@ -17673,16 +17375,17 @@ "prestashop/ps_emailsubscription": "<2.6.1", "prestashop/ps_facetedsearch": "<3.4.1", "prestashop/ps_linklist": "<3.1", - "privatebin/privatebin": "<1.4", - "processwire/processwire": "<=3.0.210", + "privatebin/privatebin": "<1.4|>=1.5,<1.7.4", + "processwire/processwire": "<=3.0.229", "propel/propel": ">=2.0.0.0-alpha1,<=2.0.0.0-alpha7", "propel/propel1": ">=1,<=1.7.1", - "pterodactyl/panel": "<1.11.6", + "pterodactyl/panel": "<1.11.8", "ptheofan/yii2-statemachine": ">=2.0.0.0-RC1-dev,<=2", "ptrofimov/beanstalk_console": "<1.7.14", "pubnub/pubnub": "<6.1", "pusher/pusher-php-server": "<2.2.1", "pwweb/laravel-core": "<=0.3.6.0-beta", + "pxlrbt/filament-excel": "<1.1.14|>=2.0.0.0-alpha,<2.3.3", "pyrocms/pyrocms": "<=3.9.1", "qcubed/qcubed": "<=3.1.1", "quickapps/cms": "<=2.0.0.0-beta2", @@ -17693,7 +17396,7 @@ "rap2hpoutre/laravel-log-viewer": "<0.13", "react/http": ">=0.7,<1.9", "really-simple-plugins/complianz-gdpr": "<6.4.2", - "redaxo/source": "<=5.15.1", + "redaxo/source": "<=5.17.1", "remdex/livehelperchat": "<4.29", "reportico-web/reportico": "<=8.1", "rhukster/dom-sanitizer": "<1.0.7", @@ -17710,12 +17413,12 @@ "serluck/phpwhois": "<=4.2.6", "sfroemken/url_redirect": "<=1.2.1", "sheng/yiicms": "<=1.2", - "shopware/core": "<6.5.8.8-dev|>=6.6.0.0-RC1-dev,<6.6.1", - "shopware/platform": "<6.5.8.8-dev|>=6.6.0.0-RC1-dev,<6.6.1", + "shopware/core": "<=6.5.8.12|>=6.6,<=6.6.5", + "shopware/platform": "<=6.5.8.12|>=6.6,<=6.6.5", "shopware/production": "<=6.3.5.2", - "shopware/shopware": "<6.2.3", + "shopware/shopware": "<=5.7.17", "shopware/storefront": "<=6.4.8.1|>=6.5.8,<6.5.8.7-dev", - "shopxo/shopxo": "<2.2.6", + "shopxo/shopxo": "<=6.1", "showdoc/showdoc": "<2.10.4", "silverstripe-australia/advancedreports": ">=1,<=2", "silverstripe/admin": "<1.13.19|>=2,<2.1.8", @@ -17723,11 +17426,12 @@ "silverstripe/cms": "<4.11.3", "silverstripe/comments": ">=1.3,<3.1.1", "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", - "silverstripe/framework": "<4.13.39|>=5,<5.1.11", + "silverstripe/framework": "<5.2.16", "silverstripe/graphql": ">=2,<2.0.5|>=3,<3.8.2|>=4,<4.3.7|>=5,<5.1.3", "silverstripe/hybridsessions": ">=1,<2.4.1|>=2.5,<2.5.1", "silverstripe/recipe-cms": ">=4.5,<4.5.3", "silverstripe/registry": ">=2.1,<2.1.2|>=2.2,<2.2.1", + "silverstripe/reports": "<5.2.3", "silverstripe/restfulserver": ">=1,<1.0.9|>=2,<2.0.4|>=2.1,<2.1.2", "silverstripe/silverstripe-omnipay": "<2.5.2|>=3,<3.0.2|>=3.1,<3.1.4|>=3.2,<3.2.1", "silverstripe/subsites": ">=2,<2.6.1", @@ -17748,7 +17452,7 @@ "slim/slim": "<2.6", "slub/slub-events": "<3.0.3", "smarty/smarty": "<4.5.3|>=5,<5.1.1", - "snipe/snipe-it": "<6.4.2", + "snipe/snipe-it": "<7.0.10", "socalnick/scn-social-auth": "<1.15.2", "socialiteproviders/steam": "<1.1", "spatie/browsershot": "<3.57.4", @@ -17757,14 +17461,16 @@ "spoon/library": "<1.4.1", "spoonity/tcpdf": "<6.2.22", "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", - "ssddanbrown/bookstack": "<22.02.3", + "ssddanbrown/bookstack": "<24.05.1", + "starcitizentools/citizen-skin": ">=2.6.3,<2.31", "statamic/cms": "<4.46|>=5.3,<5.6.2", "stormpath/sdk": "<9.9.99", - "studio-42/elfinder": "<2.1.62", + "studio-42/elfinder": "<=2.1.64", + "studiomitte/friendlycaptcha": "<0.1.4", "subhh/libconnect": "<7.0.8|>=8,<8.1", "sukohi/surpass": "<1", "sulu/form-bundle": ">=2,<2.5.3", - "sulu/sulu": "<1.6.44|>=2,<2.4.17|>=2.5,<2.5.13", + "sulu/sulu": "<1.6.44|>=2,<2.5.21|>=2.6,<2.6.5", "sumocoders/framework-user-bundle": "<1.4", "superbig/craft-audit": "<3.0.2", "swag/paypal": "<5.4.4", @@ -17775,7 +17481,7 @@ "sylius/grid-bundle": "<1.10.1", "sylius/paypal-plugin": ">=1,<1.2.4|>=1.3,<1.3.1", "sylius/resource-bundle": ">=1,<1.3.14|>=1.4,<1.4.7|>=1.5,<1.5.2|>=1.6,<1.6.4", - "sylius/sylius": "<1.9.10|>=1.10,<1.10.11|>=1.11,<1.11.2|>=1.12.0.0-alpha1,<1.12.16|>=1.13.0.0-alpha1,<1.13.1", + "sylius/sylius": "<1.12.19|>=1.13.0.0-alpha1,<1.13.4", "symbiote/silverstripe-multivaluefield": ">=3,<3.1", "symbiote/silverstripe-queuedjobs": ">=3,<3.0.2|>=3.1,<3.1.4|>=4,<4.0.7|>=4.1,<4.1.2|>=4.2,<4.2.4|>=4.3,<4.3.3|>=4.4,<4.4.3|>=4.5,<4.5.1|>=4.6,<4.6.4", "symbiote/silverstripe-seed": "<6.0.3", @@ -17786,7 +17492,8 @@ "symfony/error-handler": ">=4.4,<4.4.4|>=5,<5.0.4", "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1", "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7|>=5.3.14,<5.3.15|>=5.4.3,<5.4.4|>=6.0.3,<6.0.4", - "symfony/http-foundation": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7", + "symfony/http-client": ">=4.3,<5.4.46|>=6,<6.4.14|>=7,<7.1.7", + "symfony/http-foundation": "<5.4.46|>=6,<6.4.14|>=7,<7.1.7", "symfony/http-kernel": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", "symfony/maker-bundle": ">=1.27,<1.29.2|>=1.30,<1.31.1", @@ -17794,20 +17501,22 @@ "symfony/phpunit-bridge": ">=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", "symfony/polyfill": ">=1,<1.10", "symfony/polyfill-php55": ">=1,<1.10", + "symfony/process": "<5.4.46|>=6,<6.4.14|>=7,<7.1.7", "symfony/proxy-manager-bridge": ">=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", "symfony/routing": ">=2,<2.0.19", + "symfony/runtime": ">=5.3,<5.4.46|>=6,<6.4.14|>=7,<7.1.7", "symfony/security": ">=2,<2.7.51|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.8", - "symfony/security-bundle": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", + "symfony/security-bundle": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.4.10|>=7,<7.0.10|>=7.1,<7.1.3", "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.9", "symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", "symfony/security-guard": ">=2.8,<3.4.48|>=4,<4.4.23|>=5,<5.2.8", "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7|>=5.1,<5.2.8|>=5.3,<5.3.2|>=5.4,<5.4.31|>=6,<6.3.8", "symfony/serializer": ">=2,<2.0.11|>=4.1,<4.4.35|>=5,<5.3.12", - "symfony/symfony": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8", + "symfony/symfony": "<5.4.46|>=6,<6.4.14|>=7,<7.1.7", "symfony/translation": ">=2,<2.0.17", "symfony/twig-bridge": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8", "symfony/ux-autocomplete": "<2.11.2", - "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3", + "symfony/validator": "<5.4.43|>=6,<6.4.11|>=7,<7.1.4", "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", "symfony/webhook": ">=6.3,<6.3.8", @@ -17827,21 +17536,22 @@ "thorsten/phpmyfaq": "<3.2.2", "tikiwiki/tiki-manager": "<=17.1", "timber/timber": ">=0.16.6,<1.23.1|>=1.24,<1.24.1|>=2,<2.1", - "tinymce/tinymce": "<7", + "tinymce/tinymce": "<7.2", "tinymighty/wiki-seo": "<1.2.2", "titon/framework": "<9.9.99", "tobiasbg/tablepress": "<=2.0.0.0-RC1", - "topthink/framework": "<6.0.17|>=6.1,<6.1.5|>=8,<8.0.4", + "topthink/framework": "<6.0.17|>=6.1,<=8.0.4", "topthink/think": "<=6.1.1", - "topthink/thinkphp": "<=3.2.3", - "torrentpier/torrentpier": "<=2.4.1", + "topthink/thinkphp": "<=3.2.3|>=6.1.3,<=8.0.4", + "torrentpier/torrentpier": "<=2.4.3", "tpwd/ke_search": "<4.0.3|>=4.1,<4.6.6|>=5,<5.0.2", - "tribalsystems/zenario": "<9.5.60602", + "tribalsystems/zenario": "<=9.7.61188", "truckersmp/phpwhois": "<=4.3.1", "ttskch/pagination-service-provider": "<1", - "twig/twig": "<1.44.7|>=2,<2.15.3|>=3,<3.4.3", + "twbs/bootstrap": "<=3.4.1|>=4,<=4.6.2", + "twig/twig": "<3.11.2|>=3.12,<3.14.1", "typo3/cms": "<9.5.29|>=10,<10.4.35|>=11,<11.5.23|>=12,<12.2", - "typo3/cms-backend": "<4.1.14|>=4.2,<4.2.15|>=4.3,<4.3.7|>=4.4,<4.4.4|>=7,<=7.6.50|>=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1", + "typo3/cms-backend": "<4.1.14|>=4.2,<4.2.15|>=4.3,<4.3.7|>=4.4,<4.4.4|>=7,<=7.6.50|>=8,<=8.7.39|>=9,<=9.5.24|>=10,<10.4.46|>=11,<11.5.40|>=12,<12.4.21|>=13,<13.3.1", "typo3/cms-core": "<=8.7.56|>=9,<=9.5.47|>=10,<=10.4.44|>=11,<=11.5.36|>=12,<=12.4.14|>=13,<=13.1", "typo3/cms-extbase": "<6.2.24|>=7,<7.6.8|==8.1.1", "typo3/cms-fluid": "<4.3.4|>=4.4,<4.4.1", @@ -17858,6 +17568,7 @@ "ua-parser/uap-php": "<3.8", "uasoft-indonesia/badaso": "<=2.9.7", "unisharp/laravel-filemanager": "<2.6.4", + "unopim/unopim": "<0.1.4", "userfrosting/userfrosting": ">=0.3.1,<4.6.3", "usmanhalalit/pixie": "<1.0.3|>=2,<2.0.2", "uvdesk/community-skeleton": "<=1.1.1", @@ -17876,7 +17587,8 @@ "wallabag/tcpdf": "<6.2.22", "wallabag/wallabag": "<2.6.7", "wanglelecc/laracms": "<=1.0.3", - "web-auth/webauthn-framework": ">=3.3,<3.3.4", + "web-auth/webauthn-framework": ">=3.3,<3.3.4|>=4.5,<4.9", + "web-auth/webauthn-lib": ">=4.5,<4.9", "web-feet/coastercms": "==5.5", "webbuilders-group/silverstripe-kapost-bridge": "<0.4", "webcoast/deferred-image-processing": "<1.0.2", @@ -17890,6 +17602,7 @@ "winter/wn-dusk-plugin": "<2.1", "winter/wn-system-module": "<1.2.4", "wintercms/winter": "<=1.2.3", + "wireui/wireui": "<1.19.3|>=2,<2.1.3", "woocommerce/woocommerce": "<6.6|>=8.8,<8.8.5|>=8.9,<8.9.3", "wp-cli/wp-cli": ">=0.12,<2.5", "wp-graphql/wp-graphql": "<=1.14.5", @@ -17901,12 +17614,12 @@ "xataface/xataface": "<3", "xpressengine/xpressengine": "<3.0.15", "yab/quarx": "<2.4.5", - "yeswiki/yeswiki": "<4.1", + "yeswiki/yeswiki": "<=4.4.4", "yetiforce/yetiforce-crm": "<=6.4", "yidashi/yii2cmf": "<=2", "yii2mod/yii2-cms": "<1.9.2", "yiisoft/yii": "<1.1.29", - "yiisoft/yii2": "<2.0.50", + "yiisoft/yii2": "<2.0.49.4-dev", "yiisoft/yii2-authclient": "<2.2.15", "yiisoft/yii2-bootstrap": "<2.0.4", "yiisoft/yii2-dev": "<2.0.43", @@ -17992,7 +17705,7 @@ "type": "tidelift" } ], - "time": "2024-06-17T23:04:35+00:00" + "time": "2024-11-07T19:04:57+00:00" }, { "name": "sebastian/cli-parser", @@ -18959,16 +18672,16 @@ }, { "name": "symfony/browser-kit", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "62ab90b92066ef6cce5e79365625b4b1432464c8" + "reference": "65d4b3fd9556e4b5b41287bef93c671f8f9f86ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/62ab90b92066ef6cce5e79365625b4b1432464c8", - "reference": "62ab90b92066ef6cce5e79365625b4b1432464c8", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/65d4b3fd9556e4b5b41287bef93c671f8f9f86ab", + "reference": "65d4b3fd9556e4b5b41287bef93c671f8f9f86ab", "shasum": "" }, "require": { @@ -19007,7 +18720,7 @@ "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/browser-kit/tree/v6.4.8" + "source": "https://github.com/symfony/browser-kit/tree/v6.4.13" }, "funding": [ { @@ -19023,20 +18736,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-10-25T15:07:50+00:00" }, { "name": "symfony/debug-bundle", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/debug-bundle.git", - "reference": "689f1bcb0bd3b945e3c671cbd06274b127c64dc9" + "reference": "7bcfaff39e094cc09455201916d016d9b2ae08ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/689f1bcb0bd3b945e3c671cbd06274b127c64dc9", - "reference": "689f1bcb0bd3b945e3c671cbd06274b127c64dc9", + "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/7bcfaff39e094cc09455201916d016d9b2ae08ff", + "reference": "7bcfaff39e094cc09455201916d016d9b2ae08ff", "shasum": "" }, "require": { @@ -19081,7 +18794,7 @@ "description": "Provides a tight integration of the Symfony VarDumper component and the ServerLogCommand from MonologBridge into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/debug-bundle/tree/v6.4.8" + "source": "https://github.com/symfony/debug-bundle/tree/v6.4.13" }, "funding": [ { @@ -19097,20 +18810,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/dom-crawler", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "105b56a0305d219349edeb60a800082eca864e4b" + "reference": "ae074dffb018c37a57071990d16e6152728dd972" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/105b56a0305d219349edeb60a800082eca864e4b", - "reference": "105b56a0305d219349edeb60a800082eca864e4b", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/ae074dffb018c37a57071990d16e6152728dd972", + "reference": "ae074dffb018c37a57071990d16e6152728dd972", "shasum": "" }, "require": { @@ -19148,7 +18861,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v6.4.8" + "source": "https://github.com/symfony/dom-crawler/tree/v6.4.13" }, "funding": [ { @@ -19164,20 +18877,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-10-25T15:07:50+00:00" }, { "name": "symfony/maker-bundle", - "version": "v1.60.0", + "version": "v1.61.0", "source": { "type": "git", "url": "https://github.com/symfony/maker-bundle.git", - "reference": "c305a02a22974670f359d4274c9431e1a191f559" + "reference": "a3b7f14d349f8f44ed752d4dde2263f77510cc18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/c305a02a22974670f359d4274c9431e1a191f559", - "reference": "c305a02a22974670f359d4274c9431e1a191f559", + "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/a3b7f14d349f8f44ed752d4dde2263f77510cc18", + "reference": "a3b7f14d349f8f44ed752d4dde2263f77510cc18", "shasum": "" }, "require": { @@ -19240,7 +18953,7 @@ ], "support": { "issues": "https://github.com/symfony/maker-bundle/issues", - "source": "https://github.com/symfony/maker-bundle/tree/v1.60.0" + "source": "https://github.com/symfony/maker-bundle/tree/v1.61.0" }, "funding": [ { @@ -19256,20 +18969,20 @@ "type": "tidelift" } ], - "time": "2024-06-10T06:03:18+00:00" + "time": "2024-08-29T22:50:23+00:00" }, { "name": "symfony/phpunit-bridge", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "937f47cc64922f283bb0c474f33415bba0a9fc0d" + "reference": "e6377bea5b114f70de6332fe935e160da5014ce8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/937f47cc64922f283bb0c474f33415bba0a9fc0d", - "reference": "937f47cc64922f283bb0c474f33415bba0a9fc0d", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/e6377bea5b114f70de6332fe935e160da5014ce8", + "reference": "e6377bea5b114f70de6332fe935e160da5014ce8", "shasum": "" }, "require": { @@ -19322,7 +19035,7 @@ "description": "Provides utilities for PHPUnit, especially user deprecation notices management", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/phpunit-bridge/tree/v6.4.8" + "source": "https://github.com/symfony/phpunit-bridge/tree/v6.4.13" }, "funding": [ { @@ -19338,20 +19051,20 @@ "type": "tidelift" } ], - "time": "2024-06-02T15:48:50+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/web-profiler-bundle", - "version": "v6.4.8", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/web-profiler-bundle.git", - "reference": "bcc806d1360991de3bf78ac5ca0202db85de9bfc" + "reference": "bfbade623f1cc7f1e243ce5488af33861a8f5be7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/bcc806d1360991de3bf78ac5ca0202db85de9bfc", - "reference": "bcc806d1360991de3bf78ac5ca0202db85de9bfc", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/bfbade623f1cc7f1e243ce5488af33861a8f5be7", + "reference": "bfbade623f1cc7f1e243ce5488af33861a8f5be7", "shasum": "" }, "require": { @@ -19404,7 +19117,7 @@ "dev" ], "support": { - "source": "https://github.com/symfony/web-profiler-bundle/tree/v6.4.8" + "source": "https://github.com/symfony/web-profiler-bundle/tree/v6.4.14" }, "funding": [ { @@ -19420,20 +19133,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-11-04T11:33:53+00:00" }, { "name": "symplify/easy-coding-standard", - "version": "12.3.0", + "version": "12.3.6", "source": { "type": "git", "url": "https://github.com/easy-coding-standard/easy-coding-standard.git", - "reference": "f919574aa566b4d00fd06700ca61168aafef66e1" + "reference": "c0f378782d06dfd21c66c3024e9d28f4e737645e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/f919574aa566b4d00fd06700ca61168aafef66e1", - "reference": "f919574aa566b4d00fd06700ca61168aafef66e1", + "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/c0f378782d06dfd21c66c3024e9d28f4e737645e", + "reference": "c0f378782d06dfd21c66c3024e9d28f4e737645e", "shasum": "" }, "require": { @@ -19469,7 +19182,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.3.0" + "source": "https://github.com/easy-coding-standard/easy-coding-standard/tree/12.3.6" }, "funding": [ { @@ -19481,7 +19194,7 @@ "type": "github" } ], - "time": "2024-06-18T07:35:59+00:00" + "time": "2024-10-06T08:27:28+00:00" }, { "name": "theseer/tokenizer", diff --git a/config/bundles.php b/config/bundles.php index b78bbc22..ea066084 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -18,7 +18,6 @@ return [ DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true], Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], Gregwar\CaptchaBundle\GregwarCaptchaBundle::class => ['all' => true], - Translation\Bundle\TranslationBundle::class => ['all' => true], Florianv\SwapBundle\FlorianvSwapBundle::class => ['all' => true], Nelmio\SecurityBundle\NelmioSecurityBundle::class => ['all' => true], Symfony\UX\Turbo\TurboBundle::class => ['all' => true], @@ -32,4 +31,5 @@ return [ KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle::class => ['all' => true], Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true], ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true], + Jbtronics\TranslationEditorBundle\JbtronicsTranslationEditorBundle::class => ['dev' => true], ]; diff --git a/config/packages/datatables.yaml b/config/packages/datatables.yaml index dc116f97..63d386ee 100644 --- a/config/packages/datatables.yaml +++ b/config/packages/datatables.yaml @@ -10,13 +10,12 @@ datatables: options: lengthMenu : [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]] pageLength: '%partdb.table.default_page_size%' # Set to -1 to disable pagination (i.e. show all rows) by default - #dom: "<'row' <'col-sm-12' tr>><'row' <'col-sm-6'l><'col-sm-6 text-right'pif>>" - dom: " <'row'<'col mb-2 input-group' B l> <'col mb-2' <'pull-end' p>>> - <'card' - rt - <'card-footer card-footer-table text-muted' i > - > - <'row'<'col mt-2 input-group' B l> <'col mt-2' <'pull-right' p>>>" + dom: " <'row' <'col mb-2 input-group flex-nowrap' B l > <'col-auto mb-2' < p >>> + <'card' + rt + <'card-footer card-footer-table text-muted' i > + > + <'row' <'col mt-2 input-group flex-nowrap' B l > <'col-auto mt-2' < p >>>" pagingType: 'simple_numbers' searching: true stateSave: true diff --git a/config/packages/dev/php_translation.yaml b/config/packages/dev/php_translation.yaml deleted file mode 100644 index 53398fbc..00000000 --- a/config/packages/dev/php_translation.yaml +++ /dev/null @@ -1,5 +0,0 @@ -translation: - symfony_profiler: - enabled: true - webui: - enabled: true diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index cea0126a..0986565f 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -9,10 +9,17 @@ doctrine: # either here or in the DATABASE_URL env var (see .env file) types: + # UTC datetimes datetime: class: App\Doctrine\Types\UTCDateTimeType date: class: App\Doctrine\Types\UTCDateTimeType + + datetime_immutable: + class: App\Doctrine\Types\UTCDateTimeImmutableType + date_immutable: + class: App\Doctrine\Types\UTCDateTimeImmutableType + big_decimal: class: App\Doctrine\Types\BigDecimalType tinyint: diff --git a/config/packages/php_translation.yaml b/config/packages/php_translation.yaml deleted file mode 100644 index 7c4f6ad9..00000000 --- a/config/packages/php_translation.yaml +++ /dev/null @@ -1,11 +0,0 @@ -translation: - locales: ["en", "de"] - edit_in_place: - enabled: false - config_name: app - configs: - app: - dirs: ["%kernel.project_dir%/templates", "%kernel.project_dir%/src"] - output_dir: "%kernel.project_dir%/translations" - excluded_names: ["*TestCase.php", "*Test.php"] - excluded_dirs: [cache, data, logs] diff --git a/config/parameters.yaml b/config/parameters.yaml index 3c4b9c0a..b2c10893 100644 --- a/config/parameters.yaml +++ b/config/parameters.yaml @@ -11,7 +11,7 @@ parameters: partdb.banner: '%env(trim:string:BANNER)%' # The info text shown in the homepage, if empty config/banner.md is used partdb.default_currency: '%env(string:BASE_CURRENCY)%' # The currency that is used inside the DB (and is assumed when no currency is set). This can not be changed later, so be sure to set it the currency used in your country partdb.global_theme: '' # The theme to use globally (see public/build/themes/ for choices, use name without .css). Set to '' for default bootstrap theme - partdb.locale_menu: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh'] # The languages that are shown in user drop down menu + partdb.locale_menu: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh', 'pl'] # The languages that are shown in user drop down menu partdb.enforce_change_comments_for: '%env(csv:ENFORCE_CHANGE_COMMENTS_FOR)%' # The actions for which a change comment is required (e.g. "part_edit", "part_create", etc.). If this is empty, change comments are not required at all. partdb.default_uri: '%env(string:DEFAULT_URI)%' # The default URI to use for the Part-DB instance (e.g. https://part-db.example.com/). This is used for generating links in emails diff --git a/config/routes/dev/php_translation.yaml b/config/routes/dev/php_translation.yaml deleted file mode 100644 index 903b23fb..00000000 --- a/config/routes/dev/php_translation.yaml +++ /dev/null @@ -1,6 +0,0 @@ -_translation_webui: - resource: '@TranslationBundle/Resources/config/routing_webui.yaml' - prefix: /admin - -_translation_profiler: - resource: '@TranslationBundle/Resources/config/routing_symfony_profiler.yaml' diff --git a/config/routes/jbtronics_translation_editor.yaml b/config/routes/jbtronics_translation_editor.yaml new file mode 100644 index 00000000..31409a61 --- /dev/null +++ b/config/routes/jbtronics_translation_editor.yaml @@ -0,0 +1,3 @@ +when@dev: + translation_editor: + resource: '@JbtronicsTranslationEditorBundle/config/routes.php' \ No newline at end of file diff --git a/config/routes/php_translation.yaml b/config/routes/php_translation.yaml deleted file mode 100644 index 96ffd76f..00000000 --- a/config/routes/php_translation.yaml +++ /dev/null @@ -1,3 +0,0 @@ -_translation_edit_in_place: - resource: '@TranslationBundle/Resources/config/routing_edit_in_place.yaml' - prefix: /admin diff --git a/config/services.yaml b/config/services.yaml index 0e30ab14..b2342edd 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -76,18 +76,12 @@ services: # Only the event classes specified here are saved to DB (set to []) to log all events $whitelist: [] - App\EventSubscriber\LogSystem\EventLoggerSubscriber: + App\EventListener\LogSystem\EventLoggerListener: arguments: $save_changed_fields: '%env(bool:HISTORY_SAVE_CHANGED_FIELDS)%' $save_changed_data: '%env(bool:HISTORY_SAVE_CHANGED_DATA)%' $save_removed_data: '%env(bool:HISTORY_SAVE_REMOVED_DATA)%' $save_new_data: '%env(bool:HISTORY_SAVE_NEW_DATA)%' - tags: - - { name: 'doctrine.event_subscriber' } - - App\EventSubscriber\LogSystem\LogDBMigrationSubscriber: - tags: - - { name: 'doctrine.event_subscriber' } App\Form\AttachmentFormType: arguments: @@ -312,6 +306,16 @@ services: $enabled: '%env(bool:PROVIDER_LCSC_ENABLED)%' $currency: '%env(string:PROVIDER_LCSC_CURRENCY)%' + App\Services\InfoProviderSystem\Providers\OEMSecretsProvider: + arguments: + $api_key: '%env(string:PROVIDER_OEMSECRETS_KEY)%' + $country_code: '%env(string:PROVIDER_OEMSECRETS_COUNTRY_CODE)%' + $currency: '%env(PROVIDER_OEMSECRETS_CURRENCY)%' + $zero_price: '%env(PROVIDER_OEMSECRETS_ZERO_PRICE)%' + $set_param: '%env(PROVIDER_OEMSECRETS_SET_PARAM)%' + $sort_criteria: '%env(PROVIDER_OEMSECRETS_SORT_CRITERIA)%' + + #################################################################################################################### # API system #################################################################################################################### @@ -406,4 +410,4 @@ when@test: arguments: - '@doctrine.fixtures.loader' - '@doctrine' - - { default: '@App\Doctrine\Purger\DoNotUsePurgerFactory' } \ No newline at end of file + - { default: '@App\Doctrine\Purger\DoNotUsePurgerFactory' } diff --git a/docs/configuration.md b/docs/configuration.md index c0daa407..0ad30a00 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -32,11 +32,16 @@ options listed, see `.env` file for the full list of possible env variables. ### General options -* `DATABASE_URL`: Configures the database which Part-DB uses. For mysql use a string in the form - of `mysql://:@:/` here - (e.g. `DATABASE_URL=mysql://user:password@127.0.0.1:3306/part-db`). For SQLite use the following format to specify the +* `DATABASE_URL`: Configures the database which Part-DB uses: + * For MySQL (or MariaDB) use a string in the form of `mysql://:@:/` here + (e.g. `DATABASE_URL=mysql://user:password@127.0.0.1:3306/part-db`). + * For SQLite use the following format to specify the absolute path where it should be located `sqlite:///path/part/app.db`. You can use `%kernel.project_dir%` as placeholder for the Part-DB root folder (e.g. `sqlite:///%kernel.project_dir%/var/app.db`) + * For Postgresql use a string in the form of `DATABASE_URL=postgresql://user:password@127.0.0.1:5432/part-db?serverVersion=x.y`. + + Please note that **`serverVersion=x.y`** variable is required due to dependency of Symfony framework. + * `DATABASE_MYSQL_USE_SSL_CA`: If this value is set to `1` or `true` and a MySQL connection is used, then the connection is encrypted by SSL/TLS and the server certificate is verified against the system CA certificates or the CA certificate bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept all certificates. @@ -86,6 +91,10 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept * `datastructure_create`: Creation of a new datastructure (e.g. category, manufacturer, ...) * `CHECK_FOR_UPDATES` (default `1`): Set this to 0, if you do not want Part-DB to connect to GitHub to check for new versions, or if your server can not connect to the internet. +* `APP_SECRET`: This variable is a configuration parameter used for various security-related purposes, + particularly for securing and protecting various aspects of your application. It's a secret key that is used for + cryptographic operations and security measures (session management, CSRF protection, etc..). Therefore this + value should be handled as confidential data and not shared publicly. ### E-Mail settings @@ -243,4 +252,4 @@ The following options are available: number of sidebar panels by changing the number of items in this list. * `partdb.sidebar.root_node_enable`: Show a root node in the sidebar trees, of which all nodes are children of * `partdb.sidebar.root_expanded`: Expand the root node in the sidebar trees by default -* `partdb.available_themes`: The list of available themes a user can choose from. \ No newline at end of file +* `partdb.available_themes`: The list of available themes a user can choose from. diff --git a/docs/installation/installation_docker.md b/docs/installation/installation_docker.md index 98fb82d1..5d357ae5 100644 --- a/docs/installation/installation_docker.md +++ b/docs/installation/installation_docker.md @@ -158,7 +158,7 @@ services: container_name: partdb_database image: mysql:8.0 restart: unless-stopped - command: --default-authentication-plugin=mysql_native_password + command: --default-authentication-plugin=mysql_native_password --log-bin-trust-function-creators=1 environment: # Change this Password MYSQL_ROOT_PASSWORD: SECRET_ROOT_PASSWORD diff --git a/docs/usage/information_provider_system.md b/docs/usage/information_provider_system.md index 9abf75b8..ee9fd394 100644 --- a/docs/usage/information_provider_system.md +++ b/docs/usage/information_provider_system.md @@ -212,6 +212,26 @@ An API key is not required, it is enough to enable the provider using the follow * `PROVIDER_LCSC_ENABLED`: Set this to `1` to enable the LCSC provider * `PROVIDER_LCSC_CURRENCY`: The currency you want to get prices in (see LCSC webshop for available currencies, default: `EUR`) +### OEMsecrets + +The oemsecrets provider uses the [oemsecrets API](https://www.oemsecrets.com/) to search for parts and getting shopping +information from them. Similar to octopart it aggregates offers from different distributors. + +You can apply for a free API key on the [oemsecrets API page](https://www.oemsecrets.com/api/) and put the key you get +in the Part-DB env configuration (see below). + +The following env configuration options are available: + +* `PROVIDER_OEMSECRETS_KEY`: The API key you got from oemsecrets (mandatory) +* `PROVIDER_OEMSECRETS_COUNTRY_CODE`: The two-letter code of the country you want to get the prices for +* `PROVIDER_OEMSECRETS_CURRENCY`: The currency you want to get prices in (optional, default: `EUR`) +* `PROVIDER_OEMSECRETS_ZERO_PRICE`: If set to `1`, parts with a price of 0 will be included in the search results, otherwise + they will be excluded (optional, default: `0`) +* `PROVIDER_OEMSECRETS_SET_PARAM`: If set to `1`, the provider will try to extract parameters from the part description +* `PROVIDER_OEMSECRETS_SORT_CRITERIA`: The criteria to sort the search results by. If set to 'C', it further sorts by +completeness (prioritizing items with the most detailed information). If set to 'M', it further sorts by manufacturer name. +If set to any other value, no sorting is performed. + ### Custom provider To create a custom provider, you have to create a new class implementing the `InfoProviderInterface` interface. As long diff --git a/migrations/Version20240606203053.php b/migrations/Version20240606203053.php index 9692e146..83370ad6 100644 --- a/migrations/Version20240606203053.php +++ b/migrations/Version20240606203053.php @@ -343,6 +343,7 @@ final class Version20240606203053 extends AbstractMultiPlatformMigration impleme $this->addSql('ALTER TABLE webauthn_keys CHANGE public_key_credential_id public_key_credential_id LONGTEXT NOT NULL, CHANGE transports transports LONGTEXT NOT NULL, CHANGE trust_path trust_path JSON NOT NULL, CHANGE aaguid aaguid TINYTEXT NOT NULL, CHANGE credential_public_key credential_public_key LONGTEXT NOT NULL, CHANGE other_ui other_ui LONGTEXT DEFAULT NULL, CHANGE last_time_used last_time_used DATETIME DEFAULT NULL'); // Add the natural sort emulation function to the database (based on this stackoverflow: https://stackoverflow.com/questions/153633/natural-sort-in-mysql/58154535#58154535) + //This version here is wrong, and will be replaced by the correct version in the next migration (we need to use nowdoc instead of heredoc, otherwise the slashes will be wrongly escaped!!) $this->addSql(<<addSql('DROP FUNCTION IF EXISTS NatSortKey'); + + //The difference to the original function is the correct length of the suf variable and correct escaping + //We now use heredoc syntax to avoid escaping issues with the \ (which resulted in "range out of order in character class"). + $this->addSql(<<<'EOD' + CREATE DEFINER=CURRENT_USER FUNCTION `NatSortKey`(`s` VARCHAR(1000) CHARSET utf8mb4, `n` INT) RETURNS varchar(3500) CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci + DETERMINISTIC + SQL SECURITY INVOKER + BEGIN + /**** + Converts numbers in the input string s into a format such that sorting results in a nat-sort. + Numbers of up to 359 digits (before the decimal point, if one is present) are supported. Sort results are undefined if the input string contains numbers longer than this. + For n>0, only the first n numbers in the input string will be converted for nat-sort (so strings that differ only after the first n numbers will not nat-sort amongst themselves). + Total sort-ordering is preserved, i.e. if s1!=s2, then NatSortKey(s1,n)!=NatSortKey(s2,n), for any given n. + Numbers may contain ',' as a thousands separator, and '.' as a decimal point. To reverse these (as appropriate for some European locales), the code would require modification. + Numbers preceded by '+' sort with numbers not preceded with either a '+' or '-' sign. + Negative numbers (preceded with '-') sort before positive numbers, but are sorted in order of ascending absolute value (so -7 sorts BEFORE -1001). + Numbers with leading zeros sort after the same number with no (or fewer) leading zeros. + Decimal-part-only numbers (like .75) are recognised, provided the decimal point is not immediately preceded by either another '.', or by a letter-type character. + Numbers with thousand separators sort after the same number without them. + Thousand separators are only recognised in numbers with no leading zeros that don't immediately follow a ',', and when they format the number correctly. + (When not recognised as a thousand separator, a ',' will instead be treated as separating two distinct numbers). + Version-number-like sequences consisting of 3 or more numbers separated by '.' are treated as distinct entities, and each component number will be nat-sorted. + The entire entity will sort after any number beginning with the first component (so e.g. 10.2.1 sorts after both 10 and 10.995, but before 11) + Note that The first number component in an entity like this is also permitted to contain thousand separators. + + To achieve this, numbers within the input string are prefixed and suffixed according to the following format: + - The number is prefixed by a 2-digit base-36 number representing its length, excluding leading zeros. If there is a decimal point, this length only includes the integer part of the number. + - A 3-character suffix is appended after the number (after the decimals if present). + - The first character is a space, or a '+' sign if the number was preceded by '+'. Any preceding '+' sign is also removed from the front of the number. + - This is followed by a 2-digit base-36 number that encodes the number of leading zeros and whether the number was expressed in comma-separated form (e.g. 1,000,000.25 vs 1000000.25) + - The value of this 2-digit number is: (number of leading zeros)*2 + (1 if comma-separated, 0 otherwise) + - For version number sequences, each component number has the prefix in front of it, and the separating dots are removed. + Then there is a single suffix that consists of a ' ' or '+' character, followed by a pair base-36 digits for each number component in the sequence. + + e.g. here is how some simple sample strings get converted: + 'Foo055' --> 'Foo0255 02' + 'Absolute zero is around -273 centigrade' --> 'Absolute zero is around -03273 00 centigrade' + 'The $1,000,000 prize' --> 'The $071000000 01 prize' + '+99.74 degrees' --> '0299.74+00 degrees' + 'I have 0 apples' --> 'I have 00 02 apples' + '.5 is the same value as 0000.5000' --> '00.5 00 is the same value as 00.5000 08' + 'MariaDB v10.3.0018' --> 'MariaDB v02100130218 000004' + + The restriction to numbers of up to 359 digits comes from the fact that the first character of the base-36 prefix MUST be a decimal digit, and so the highest permitted prefix value is '9Z' or 359 decimal. + The code could be modified to handle longer numbers by increasing the size of (both) the prefix and suffix. + A higher base could also be used (by replacing CONV() with a custom function), provided that the collation you are using sorts the "digits" of the base in the correct order, starting with 0123456789. + However, while the maximum number length may be increased this way, note that the technique this function uses is NOT applicable where strings may contain numbers of unlimited length. + + The function definition does not specify the charset or collation to be used for string-type parameters or variables: The default database charset & collation at the time the function is defined will be used. + This is to make the function code more portable. However, there are some important restrictions: + + - Collation is important here only when comparing (or storing) the output value from this function, but it MUST order the characters " +0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" in that order for the natural sort to work. + This is true for most collations, but not all of them, e.g. in Lithuanian 'Y' comes before 'J' (according to Wikipedia). + To adapt the function to work with such collations, replace CONV() in the function code with a custom function that emits "digits" above 9 that are characters ordered according to the collation in use. + + - For efficiency, the function code uses LENGTH() rather than CHAR_LENGTH() to measure the length of strings that consist only of digits 0-9, '.', and ',' characters. + This works for any single-byte charset, as well as any charset that maps standard ASCII characters to single bytes (such as utf8 or utf8mb4). + If using a charset that maps these characters to multiple bytes (such as, e.g. utf16 or utf32), you MUST replace all instances of LENGTH() in the function definition with CHAR_LENGTH() + + Length of the output: + + Each number converted adds 5 characters (2 prefix + 3 suffix) to the length of the string. n is the maximum count of numbers to convert; + This parameter is provided as a means to limit the maximum output length (to input length + 5*n). + If you do not require the total-ordering property, you could edit the code to use suffixes of 1 character (space or plus) only; this would reduce the maximum output length for any given n. + Since a string of length L has at most ((L+1) DIV 2) individual numbers in it (every 2nd character a digit), for n<=0 the maximum output length is (inputlength + 5*((inputlength+1) DIV 2)) + So for the current input length of 100, the maximum output length is 350. + If changing the input length, the output length must be modified according to the above formula. The DECLARE statements for x,y,r, and suf must also be modified, as the code comments indicate. + ****/ + + DECLARE x,y varchar(1000); # need to be same length as input s + DECLARE r varchar(3500) DEFAULT ''; # return value: needs to be same length as return type + DECLARE suf varchar(1001); # suffix for a number or version string. Must be (((inputlength+1) DIV 2)*2 + 1) chars to support version strings (e.g. '1.2.33.5'), though it's usually just 3 chars. (Max version string e.g. 1.2. ... .5 has ((length of input + 1) DIV 2) numeric components) + DECLARE i,j,k int UNSIGNED; + IF n<=0 THEN SET n := -1; END IF; # n<=0 means "process all numbers" + LOOP + SET i := REGEXP_INSTR(s,'\\d'); # find position of next digit + IF i=0 OR n=0 THEN RETURN CONCAT(r,s); END IF; # no more numbers to process -> we're done + SET n := n-1, suf := ' '; + IF i>1 THEN + IF SUBSTRING(s,i-1,1)='.' AND (i=2 OR SUBSTRING(s,i-2,1) RLIKE '[^.\\p{L}\\p{N}\\p{M}\\x{608}\\x{200C}\\x{200D}\\x{2100}-\\x{214F}\\x{24B6}-\\x{24E9}\\x{1F130}-\\x{1F149}\\x{1F150}-\\x{1F169}\\x{1F170}-\\x{1F189}]') AND (SUBSTRING(s,i) NOT RLIKE '^\\d++\\.\\d') THEN SET i:=i-1; END IF; # Allow decimal number (but not version string) to begin with a '.', provided preceding char is neither another '.', nor a member of the unicode character classes: "Alphabetic", "Letter", "Block=Letterlike Symbols" "Number", "Mark", "Join_Control" + IF i>1 AND SUBSTRING(s,i-1,1)='+' THEN SET suf := '+', j := i-1; ELSE SET j := i; END IF; # move any preceding '+' into the suffix, so equal numbers with and without preceding "+" signs sort together + SET r := CONCAT(r,SUBSTRING(s,1,j-1)); SET s = SUBSTRING(s,i); # add everything before the number to r and strip it from the start of s; preceding '+' is dropped (not included in either r or s) + END IF; + SET x := REGEXP_SUBSTR(s,IF(SUBSTRING(s,1,1) IN ('0','.') OR (SUBSTRING(r,-1)=',' AND suf=' '),'^\\d*+(?:\\.\\d++)*','^(?:[1-9]\\d{0,2}(?:,\\d{3}(?!\\d))++|\\d++)(?:\\.\\d++)*+')); # capture the number + following decimals (including multiple consecutive '.' sequences) + SET s := SUBSTRING(s,CHAR_LENGTH(x)+1); # NOTE: CHAR_LENGTH() can be safely used instead of CHAR_LENGTH() here & below PROVIDED we're using a charset that represents digits, ',' and '.' characters using single bytes (e.g. latin1, utf8) + SET i := INSTR(x,'.'); + IF i=0 THEN SET y := ''; ELSE SET y := SUBSTRING(x,i); SET x := SUBSTRING(x,1,i-1); END IF; # move any following decimals into y + SET i := CHAR_LENGTH(x); + SET x := REPLACE(x,',',''); + SET j := CHAR_LENGTH(x); + SET x := TRIM(LEADING '0' FROM x); # strip leading zeros + SET k := CHAR_LENGTH(x); + SET suf := CONCAT(suf,LPAD(CONV(LEAST((j-k)*2,1294) + IF(i=j,0,1),10,36),2,'0')); # (j-k)*2 + IF(i=j,0,1) = (count of leading zeros)*2 + (1 if there are thousands-separators, 0 otherwise) Note the first term is bounded to <= base-36 'ZY' as it must fit within 2 characters + SET i := LOCATE('.',y,2); + IF i=0 THEN + SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x,y,suf); # k = count of digits in number, bounded to be <= '9Z' base-36 + ELSE # encode a version number (like 3.12.707, etc) + SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x); # k = count of digits in number, bounded to be <= '9Z' base-36 + WHILE CHAR_LENGTH(y)>0 AND n!=0 DO + IF i=0 THEN SET x := SUBSTRING(y,2); SET y := ''; ELSE SET x := SUBSTRING(y,2,i-2); SET y := SUBSTRING(y,i); SET i := LOCATE('.',y,2); END IF; + SET j := CHAR_LENGTH(x); + SET x := TRIM(LEADING '0' FROM x); # strip leading zeros + SET k := CHAR_LENGTH(x); + SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x); # k = count of digits in number, bounded to be <= '9Z' base-36 + SET suf := CONCAT(suf,LPAD(CONV(LEAST((j-k)*2,1294),10,36),2,'0')); # (j-k)*2 = (count of leading zeros)*2, bounded to fit within 2 base-36 digits + SET n := n-1; + END WHILE; + SET r := CONCAT(r,y,suf); + END IF; + END LOOP; + END + EOD + ); + } + + public function mySQLDown(Schema $schema): void + { + //Not needed + } + + public function sqLiteUp(Schema $schema): void + { + //Not needed + } + + public function sqLiteDown(Schema $schema): void + { + //Not needed + } + + public function postgreSQLUp(Schema $schema): void + { + //Not needed + } + + public function postgreSQLDown(Schema $schema): void + { + //Not needed + } +} diff --git a/phpstan.dist.neon b/phpstan.dist.neon index 646d29e6..a9f11291 100644 --- a/phpstan.dist.neon +++ b/phpstan.dist.neon @@ -11,6 +11,8 @@ parameters: - src/Configuration/* - src/Doctrine/Purger/* - src/DataTables/Adapters/TwoStepORMAdapter.php + - src/Form/Fixes/* + - src/Translation/Fixes/* diff --git a/rector.php b/rector.php index 232ee1e4..40eee9f7 100644 --- a/rector.php +++ b/rector.php @@ -2,12 +2,17 @@ declare(strict_types=1); +use Rector\CodeQuality\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector; use Rector\CodingStyle\Rector\FuncCall\CountArrayToEmptyArrayComparisonRector; use Rector\Config\RectorConfig; use Rector\Doctrine\Set\DoctrineSetList; +use Rector\PHPUnit\CodeQuality\Rector\Class_\PreferPHPUnitThisCallRector; use Rector\PHPUnit\Set\PHPUnitSetList; use Rector\Set\ValueObject\LevelSetList; use Rector\Set\ValueObject\SetList; +use Rector\Symfony\CodeQuality\Rector\Class_\EventListenerToEventSubscriberRector; +use Rector\Symfony\CodeQuality\Rector\ClassMethod\ActionSuffixRemoverRector; +use Rector\Symfony\CodeQuality\Rector\MethodCall\LiteralGetToRequestClassConstantRector; use Rector\Symfony\Set\SymfonySetList; use Rector\TypeDeclaration\Rector\StmtsAwareInterface\DeclareStrictTypesRector; @@ -55,5 +60,22 @@ return static function (RectorConfig $rectorConfig): void { $rectorConfig->skip([ CountArrayToEmptyArrayComparisonRector::class, + //Leave our !== null checks alone + FlipTypeControlToUseExclusiveTypeRector::class, + //Leave our PartList TableAction alone + ActionSuffixRemoverRector::class, + //We declare event listeners via attributes, therefore no need to migrate them to subscribers + EventListenerToEventSubscriberRector::class, + PreferPHPUnitThisCallRector::class, + //Do not replace 'GET' with class constant, + LiteralGetToRequestClassConstantRector::class, + ]); + + //Do not apply rules to Symfony own files + $rectorConfig->skip([ + __DIR__ . '/public/index.php', + __DIR__ . '/src/Kernel.php', + __DIR__ . '/config/preload.php', + __DIR__ . '/config/bundles.php', ]); }; diff --git a/src/ApiPlatform/Filter/EntityFilterHelper.php b/src/ApiPlatform/Filter/EntityFilterHelper.php index ddb55ae7..42cc567f 100644 --- a/src/ApiPlatform/Filter/EntityFilterHelper.php +++ b/src/ApiPlatform/Filter/EntityFilterHelper.php @@ -81,12 +81,12 @@ class EntityFilterHelper public function getDescription(array $properties): array { - if (!$properties) { + if ($properties === []) { return []; } $description = []; - foreach ($properties as $property => $strategy) { + foreach (array_keys($properties) as $property) { $description[(string)$property] = [ 'property' => $property, 'type' => Type::BUILTIN_TYPE_STRING, diff --git a/src/ApiPlatform/Filter/LikeFilter.php b/src/ApiPlatform/Filter/LikeFilter.php index 90ac8e59..f88c89b1 100644 --- a/src/ApiPlatform/Filter/LikeFilter.php +++ b/src/ApiPlatform/Filter/LikeFilter.php @@ -61,7 +61,7 @@ final class LikeFilter extends AbstractFilter } $description = []; - foreach ($this->properties as $property => $strategy) { + foreach (array_keys($this->properties) as $property) { $description[(string)$property] = [ 'property' => $property, 'type' => Type::BUILTIN_TYPE_STRING, diff --git a/src/ApiPlatform/Filter/TagFilter.php b/src/ApiPlatform/Filter/TagFilter.php new file mode 100644 index 00000000..9702cfcf --- /dev/null +++ b/src/ApiPlatform/Filter/TagFilter.php @@ -0,0 +1,102 @@ +. + */ + +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; + +/** + * Due to their nature, tags are stored in a single string, separated by commas, which requires some more complex search logic. + * This filter allows to easily search for tags in a part entity. + */ +final class TagFilter extends AbstractFilter +{ + + protected function filterProperty( + string $property, + $value, + QueryBuilder $queryBuilder, + QueryNameGeneratorInterface $queryNameGenerator, + string $resourceClass, + ?Operation $operation = null, + array $context = [] + ): void { + // Ignore filter if property is not enabled or mapped + if ( + !$this->isPropertyEnabled($property, $resourceClass) || + !$this->isPropertyMapped($property, $resourceClass) + ) { + return; + } + + //Escape any %, _ or \ in the tag + $value = addcslashes($value, '%_\\'); + + $tag_identifier_prefix = $queryNameGenerator->generateParameterName($property); + + $expr = $queryBuilder->expr(); + + $tmp = $expr->orX( + $expr->like('o.'.$property, ':' . $tag_identifier_prefix . '_1'), + $expr->like('o.'.$property, ':' . $tag_identifier_prefix . '_2'), + $expr->like('o.'.$property, ':' . $tag_identifier_prefix . '_3'), + $expr->eq('o.'.$property, ':' . $tag_identifier_prefix . '_4'), + ); + + $queryBuilder->andWhere($tmp); + + //Set the parameters for the LIKE expression, in each variation of the tag (so with a comma, at the end, at the beginning, and on both ends, and equaling the tag) + $queryBuilder->setParameter($tag_identifier_prefix . '_1', '%,' . $value . ',%'); + $queryBuilder->setParameter($tag_identifier_prefix . '_2', '%,' . $value); + $queryBuilder->setParameter($tag_identifier_prefix . '_3', $value . ',%'); + $queryBuilder->setParameter($tag_identifier_prefix . '_4', $value); + } + + public function getDescription(string $resourceClass): array + { + if (!$this->properties) { + return []; + } + + $description = []; + foreach (array_keys($this->properties) as $property) { + $description[(string)$property] = [ + 'property' => $property, + 'type' => Type::BUILTIN_TYPE_STRING, + 'required' => false, + 'description' => 'Filter for tags of a part', + '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/Command/BackupCommand.php b/src/Command/BackupCommand.php index bd647796..085c552a 100644 --- a/src/Command/BackupCommand.php +++ b/src/Command/BackupCommand.php @@ -52,6 +52,7 @@ class BackupCommand extends Command $backup_attachments = $input->getOption('attachments'); $backup_config = $input->getOption('config'); $backup_full = $input->getOption('full'); + $overwrite = $input->getOption('overwrite'); if ($backup_full) { $backup_database = true; @@ -70,7 +71,9 @@ class BackupCommand extends Command //Check if the file already exists //Then ask the user, if he wants to overwrite the file - if (file_exists($output_filepath) && !$io->confirm('The file '.realpath($output_filepath).' already exists. Do you want to overwrite it?', false)) { + if (!$overwrite + && file_exists($output_filepath) + && !$io->confirm('The file '.realpath($output_filepath).' already exists. Do you want to overwrite it?', false)) { $io->error('Backup aborted!'); return Command::FAILURE; } diff --git a/src/Command/User/SetPasswordCommand.php b/src/Command/User/SetPasswordCommand.php index 822277f1..30ef867b 100644 --- a/src/Command/User/SetPasswordCommand.php +++ b/src/Command/User/SetPasswordCommand.php @@ -83,6 +83,19 @@ class SetPasswordCommand extends Command while (!$success) { $pw1 = $io->askHidden('Please enter new password:'); + + if ($pw1 === null) { + $io->error('No password entered! Please try again.'); + + //If we are in non-interactive mode, we can not ask again + if (!$input->isInteractive()) { + $io->warning('Non-interactive mode detected. No password can be entered that way! If you are using docker exec, please use -it flag.'); + return Command::FAILURE; + } + + continue; + } + $pw2 = $io->askHidden('Please confirm:'); if ($pw1 !== $pw2) { $io->error('The entered password did not match! Please try again.'); diff --git a/src/Controller/AdminPages/BaseAdminController.php b/src/Controller/AdminPages/BaseAdminController.php index 8f3bb902..3c219e29 100644 --- a/src/Controller/AdminPages/BaseAdminController.php +++ b/src/Controller/AdminPages/BaseAdminController.php @@ -35,6 +35,7 @@ use App\Entity\LabelSystem\LabelProcessMode; use App\Entity\LabelSystem\LabelProfile; use App\Entity\Parameters\AbstractParameter; use App\Exceptions\AttachmentDownloadException; +use App\Exceptions\TwigModeException; use App\Form\AdminPages\ImportType; use App\Form\AdminPages\MassCreationForm; use App\Repository\AbstractPartsContainingRepository; @@ -52,8 +53,8 @@ use Doctrine\ORM\EntityManagerInterface; use InvalidArgumentException; use Omines\DataTablesBundle\DataTableFactory; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -61,6 +62,8 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\Serializer\Exception\UnexpectedValueException; +use Symfony\Component\Validator\ConstraintViolationInterface; +use Symfony\Component\Validator\ConstraintViolationListInterface; use Symfony\Contracts\Translation\TranslatorInterface; use function Symfony\Component\Translation\t; @@ -74,15 +77,10 @@ abstract class BaseAdminController extends AbstractController protected string $attachment_class = ''; protected ?string $parameter_class = ''; - /** - * @var EventDispatcher|EventDispatcherInterface - */ - protected EventDispatcher|EventDispatcherInterface $eventDispatcher; - public function __construct(protected TranslatorInterface $translator, protected UserPasswordHasherInterface $passwordEncoder, protected AttachmentSubmitHandler $attachmentSubmitHandler, protected EventCommentHelper $commentHelper, protected HistoryHelper $historyHelper, protected TimeTravel $timeTravel, - protected DataTableFactory $dataTableFactory, EventDispatcherInterface $eventDispatcher, protected LabelExampleElementsGenerator $barcodeExampleGenerator, + protected DataTableFactory $dataTableFactory, protected EventDispatcherInterface $eventDispatcher, protected LabelExampleElementsGenerator $barcodeExampleGenerator, protected LabelGenerator $labelGenerator, protected EntityManagerInterface $entityManager) { if ('' === $this->entity_class || '' === $this->form_class || '' === $this->twig_template || '' === $this->route_base) { @@ -96,7 +94,6 @@ abstract class BaseAdminController extends AbstractController if ('' === $this->parameter_class || ($this->parameter_class && !is_a($this->parameter_class, AbstractParameter::class, true))) { throw new InvalidArgumentException('You have to override the $parameter_class value with a valid Parameter class in your subclass!'); } - $this->eventDispatcher = $eventDispatcher; } protected function revertElementIfNeeded(AbstractDBElement $entity, ?string $timestamp): ?DateTime @@ -192,10 +189,8 @@ abstract class BaseAdminController extends AbstractController } //Ensure that the master picture is still part of the attachments - if ($entity instanceof AttachmentContainingDBElement) { - if ($entity->getMasterPictureAttachment() !== null && !$entity->getAttachments()->contains($entity->getMasterPictureAttachment())) { - $entity->setMasterPictureAttachment(null); - } + if ($entity instanceof AttachmentContainingDBElement && ($entity->getMasterPictureAttachment() !== null && !$entity->getAttachments()->contains($entity->getMasterPictureAttachment()))) { + $entity->setMasterPictureAttachment(null); } $this->commentHelper->setMessage($form['log_comment']->getData()); @@ -218,7 +213,12 @@ abstract class BaseAdminController extends AbstractController //Show preview for LabelProfile if needed. if ($entity instanceof LabelProfile) { $example = $this->barcodeExampleGenerator->getElement($entity->getOptions()->getSupportedElement()); - $pdf_data = $this->labelGenerator->generateLabel($entity->getOptions(), $example); + $pdf_data = null; + try { + $pdf_data = $this->labelGenerator->generateLabel($entity->getOptions(), $example); + } catch (TwigModeException $exception) { + $form->get('options')->get('lines')->addError(new FormError($exception->getSafeMessage())); + } } /** @var AbstractPartsContainingRepository $repo */ @@ -283,10 +283,8 @@ abstract class BaseAdminController extends AbstractController } //Ensure that the master picture is still part of the attachments - if ($new_entity instanceof AttachmentContainingDBElement) { - if ($new_entity->getMasterPictureAttachment() !== null && !$new_entity->getAttachments()->contains($new_entity->getMasterPictureAttachment())) { - $new_entity->setMasterPictureAttachment(null); - } + if ($new_entity instanceof AttachmentContainingDBElement && ($new_entity->getMasterPictureAttachment() !== null && !$new_entity->getAttachments()->contains($new_entity->getMasterPictureAttachment()))) { + $new_entity->setMasterPictureAttachment(null); } $this->commentHelper->setMessage($form['log_comment']->getData()); @@ -333,8 +331,8 @@ abstract class BaseAdminController extends AbstractController try { $errors = $importer->importFileAndPersistToDB($file, $options); - foreach ($errors as $name => $error) { - foreach ($error as $violation) { + foreach ($errors as $name => ['violations' => $violations]) { + foreach ($violations as $violation) { $this->addFlash('error', $name.': '.$violation->getMessage()); } } @@ -344,6 +342,7 @@ abstract class BaseAdminController extends AbstractController } } + ret: //Mass creation form $mass_creation_form = $this->createForm(MassCreationForm::class, ['entity_class' => $this->entity_class]); $mass_creation_form->handleRequest($request); @@ -356,11 +355,14 @@ abstract class BaseAdminController extends AbstractController $results = $importer->massCreation($data['lines'], $this->entity_class, $data['parent'] ?? null, $errors); //Show errors to user: - foreach ($errors as $error) { - if ($error['entity'] instanceof AbstractStructuralDBElement) { - $this->addFlash('error', $error['entity']->getFullPath().':'.$error['violations']); - } else { //When we don't have a structural element, we can only show the name - $this->addFlash('error', $error['entity']->getName().':'.$error['violations']); + foreach ($errors as ['entity' => $new_entity, 'violations' => $violations]) { + /** @var ConstraintViolationInterface $violation */ + foreach ($violations as $violation) { + if ($new_entity instanceof AbstractStructuralDBElement) { + $this->addFlash('error', $new_entity->getFullPath().':'.$violation->getMessage()); + } else { //When we don't have a structural element, we can only show the name + $this->addFlash('error', $new_entity->getName().':'.$violation->getMessage()); + } } } @@ -371,11 +373,10 @@ abstract class BaseAdminController extends AbstractController $em->flush(); if (count($results) > 0) { - $this->addFlash('success', t('entity.mass_creation_flash', ['%COUNT%' => count($results)])); + $this->addFlash('success', t('entity.mass_creation_flash', ['%COUNT%' => count($results)])); } } - ret: return $this->render($this->twig_template, [ 'entity' => $new_entity, 'form' => $form, diff --git a/src/Controller/AttachmentFileController.php b/src/Controller/AttachmentFileController.php index 5ea62aeb..936d27c5 100644 --- a/src/Controller/AttachmentFileController.php +++ b/src/Controller/AttachmentFileController.php @@ -52,11 +52,11 @@ class AttachmentFileController extends AbstractController } if ($attachment->isExternal()) { - throw new RuntimeException('You can not download external attachments!'); + throw $this->createNotFoundException('The file for this attachment is external and can not stored locally!'); } if (!$helper->isFileExisting($attachment)) { - throw new RuntimeException('The file associated with the attachment is not existing!'); + throw $this->createNotFoundException('The file associated with the attachment is not existing!'); } $file_path = $helper->toAbsoluteFilePath($attachment); @@ -81,11 +81,11 @@ class AttachmentFileController extends AbstractController } if ($attachment->isExternal()) { - throw new RuntimeException('You can not download external attachments!'); + throw $this->createNotFoundException('The file for this attachment is external and can not stored locally!'); } if (!$helper->isFileExisting($attachment)) { - throw new RuntimeException('The file associated with the attachment is not existing!'); + throw $this->createNotFoundException('The file associated with the attachment is not existing!'); } $file_path = $helper->toAbsoluteFilePath($attachment); diff --git a/src/Controller/KiCadApiController.php b/src/Controller/KiCadApiController.php index a45e71dd..c28e87a6 100644 --- a/src/Controller/KiCadApiController.php +++ b/src/Controller/KiCadApiController.php @@ -30,6 +30,9 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; +/** + * @see \App\Tests\Controller\KiCadApiControllerTest + */ #[Route('/kicad-api/v1')] class KiCadApiController extends AbstractController { @@ -62,7 +65,7 @@ class KiCadApiController extends AbstractController #[Route('/parts/category/{category}.json', name: 'kicad_api_category')] public function categoryParts(?Category $category): Response { - if ($category) { + if ($category !== null) { $this->denyAccessUnlessGranted('read', $category); } else { $this->denyAccessUnlessGranted('@categories.read'); diff --git a/src/Controller/LabelController.php b/src/Controller/LabelController.php index ab38e49f..d1bcfdbf 100644 --- a/src/Controller/LabelController.php +++ b/src/Controller/LabelController.php @@ -117,7 +117,7 @@ class LabelController extends AbstractController $pdf_data = $this->labelGenerator->generateLabel($form_options, $targets); $filename = $this->getLabelName($targets[0], $profile); } catch (TwigModeException $exception) { - $form->get('options')->get('lines')->addError(new FormError($exception->getMessage())); + $form->get('options')->get('lines')->addError(new FormError($exception->getSafeMessage())); } } else { //$this->addFlash('warning', 'label_generator.no_entities_found'); diff --git a/src/Controller/OAuthClientController.php b/src/Controller/OAuthClientController.php index 388c7e16..9606a4e4 100644 --- a/src/Controller/OAuthClientController.php +++ b/src/Controller/OAuthClientController.php @@ -51,7 +51,7 @@ class OAuthClientController extends AbstractController } #[Route('/{name}/check', name: 'oauth_client_check')] - public function check(string $name, Request $request): Response + public function check(string $name): Response { $this->denyAccessUnlessGranted('@system.manage_oauth_tokens'); diff --git a/src/Controller/PartController.php b/src/Controller/PartController.php index 20b07fd9..d6acb36b 100644 --- a/src/Controller/PartController.php +++ b/src/Controller/PartController.php @@ -329,7 +329,7 @@ class PartController extends AbstractController $this->em->flush(); if ($mode === 'new') { $this->addFlash('success', 'part.created_flash'); - } else if ($mode === 'edit') { + } elseif ($mode === 'edit') { $this->addFlash('success', 'part.edited_flash'); } @@ -358,11 +358,11 @@ class PartController extends AbstractController $template = ''; if ($mode === 'new') { $template = 'parts/edit/new_part.html.twig'; - } else if ($mode === 'edit') { + } elseif ($mode === 'edit') { $template = 'parts/edit/edit_part_info.html.twig'; - } else if ($mode === 'merge') { + } elseif ($mode === 'merge') { $template = 'parts/edit/merge_parts.html.twig'; - } else if ($mode === 'update_from_ip') { + } elseif ($mode === 'update_from_ip') { $template = 'parts/edit/update_from_ip.html.twig'; } diff --git a/src/Controller/ProjectController.php b/src/Controller/ProjectController.php index 9afdb645..761e498c 100644 --- a/src/Controller/ProjectController.php +++ b/src/Controller/ProjectController.php @@ -219,7 +219,7 @@ class ProjectController extends AbstractController 'project' => $project, 'part' => $part ]); - if ($bom_entry) { + if ($bom_entry !== null) { $preset_data->add($bom_entry); } else { //Otherwise create an empty one $entry = new ProjectBOMEntry(); diff --git a/src/Controller/ScanController.php b/src/Controller/ScanController.php index 93fbb822..77183c89 100644 --- a/src/Controller/ScanController.php +++ b/src/Controller/ScanController.php @@ -54,6 +54,9 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Attribute\MapQueryParameter; use Symfony\Component\Routing\Attribute\Route; +/** + * @see \App\Tests\Controller\ScanControllerTest + */ #[Route(path: '/scan')] class ScanController extends AbstractController { diff --git a/src/Controller/ToolsController.php b/src/Controller/ToolsController.php index 984e50eb..dbcb91a1 100644 --- a/src/Controller/ToolsController.php +++ b/src/Controller/ToolsController.php @@ -25,12 +25,14 @@ namespace App\Controller; use App\Services\Attachments\AttachmentSubmitHandler; use App\Services\Attachments\AttachmentURLGenerator; use App\Services\Attachments\BuiltinAttachmentsFinder; +use App\Services\Doctrine\DBInfoHelper; +use App\Services\Doctrine\NatsortDebugHelper; use App\Services\Misc\GitVersionInfo; -use App\Services\Misc\DBInfoHelper; use App\Services\System\UpdateAvailableManager; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Runtime\SymfonyRuntime; #[Route(path: '/tools')] class ToolsController extends AbstractController @@ -44,7 +46,7 @@ class ToolsController extends AbstractController } #[Route(path: '/server_infos', name: 'tools_server_infos')] - public function systemInfos(GitVersionInfo $versionInfo, DBInfoHelper $DBInfoHelper, + public function systemInfos(GitVersionInfo $versionInfo, DBInfoHelper $DBInfoHelper, NatsortDebugHelper $natsortDebugHelper, AttachmentSubmitHandler $attachmentSubmitHandler, UpdateAvailableManager $updateAvailableManager): Response { $this->denyAccessUnlessGranted('@system.server_infos'); @@ -59,10 +61,10 @@ class ToolsController extends AbstractController 'default_theme' => $this->getParameter('partdb.global_theme'), 'enabled_locales' => $this->getParameter('partdb.locale_menu'), 'demo_mode' => $this->getParameter('partdb.demo_mode'), - 'gpdr_compliance' => $this->getParameter('partdb.gdpr_compliance'), + 'gdpr_compliance' => $this->getParameter('partdb.gdpr_compliance'), 'use_gravatar' => $this->getParameter('partdb.users.use_gravatar'), 'email_password_reset' => $this->getParameter('partdb.users.email_pw_reset'), - 'enviroment' => $this->getParameter('kernel.environment'), + 'environment' => $this->getParameter('kernel.environment'), 'is_debug' => $this->getParameter('kernel.debug'), 'email_sender' => $this->getParameter('partdb.mail.sender_email'), 'email_sender_name' => $this->getParameter('partdb.mail.sender_name'), @@ -84,7 +86,7 @@ class ToolsController extends AbstractController 'php_post_max_size' => ini_get('post_max_size'), 'kernel_runtime_environment' => $this->getParameter('kernel.runtime_environment'), 'kernel_runtime_mode' => $this->getParameter('kernel.runtime_mode'), - 'kernel_runtime' => $_SERVER['APP_RUNTIME'] ?? $_ENV['APP_RUNTIME'] ?? 'Symfony\\Component\\Runtime\\SymfonyRuntime', + 'kernel_runtime' => $_SERVER['APP_RUNTIME'] ?? $_ENV['APP_RUNTIME'] ?? SymfonyRuntime::class, //DB section 'db_type' => $DBInfoHelper->getDatabaseType() ?? 'Unknown', @@ -92,6 +94,8 @@ class ToolsController extends AbstractController 'db_size' => $DBInfoHelper->getDatabaseSize(), 'db_name' => $DBInfoHelper->getDatabaseName() ?? 'Unknown', 'db_user' => $DBInfoHelper->getDatabaseUsername() ?? 'Unknown', + 'db_natsort_method' => $natsortDebugHelper->getNaturalSortMethod(), + 'db_natsort_slow_allowed' => $natsortDebugHelper->isSlowNaturalSortAllowed(), //New version section 'new_version_available' => $updateAvailableManager->isUpdateAvailable(), diff --git a/src/Controller/UserSettingsController.php b/src/Controller/UserSettingsController.php index 364cea58..f84547dc 100644 --- a/src/Controller/UserSettingsController.php +++ b/src/Controller/UserSettingsController.php @@ -38,7 +38,6 @@ use Doctrine\ORM\EntityManagerInterface; use RuntimeException; use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticatorInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Form\Extension\Core\Type\DateTimeType; use Symfony\Component\Form\Extension\Core\Type\EnumType; @@ -59,11 +58,8 @@ use Symfony\Component\Validator\Constraints\Length; #[Route(path: '/user')] class UserSettingsController extends AbstractController { - protected EventDispatcher|EventDispatcherInterface $eventDispatcher; - - public function __construct(protected bool $demo_mode, EventDispatcherInterface $eventDispatcher) + public function __construct(protected bool $demo_mode, protected EventDispatcherInterface $eventDispatcher) { - $this->eventDispatcher = $eventDispatcher; } #[Route(path: '/2fa_backup_codes', name: 'show_backup_codes')] diff --git a/src/DataFixtures/APITokenFixtures.php b/src/DataFixtures/APITokenFixtures.php index fbb4bdd2..4bcf3a60 100644 --- a/src/DataFixtures/APITokenFixtures.php +++ b/src/DataFixtures/APITokenFixtures.php @@ -75,7 +75,7 @@ class APITokenFixtures extends Fixture implements DependentFixtureInterface $expired_token->setUser($admin_user); $expired_token->setLevel(ApiTokenLevel::FULL); $expired_token->setName('expired'); - $expired_token->setValidUntil(new \DateTime('-1 day')); + $expired_token->setValidUntil(new \DateTimeImmutable('-1 day')); $this->setTokenSecret($expired_token, self::TOKEN_EXPIRED); $manager->persist($expired_token); diff --git a/src/DataFixtures/DataStructureFixtures.php b/src/DataFixtures/DataStructureFixtures.php index 72c57077..fc713d4d 100644 --- a/src/DataFixtures/DataStructureFixtures.php +++ b/src/DataFixtures/DataStructureFixtures.php @@ -74,30 +74,37 @@ class DataStructureFixtures extends Fixture implements DependentFixtureInterface /** @var AbstractStructuralDBElement $node1 */ $node1 = new $class(); $node1->setName('Node 1'); + $this->addReference($class . '_1', $node1); /** @var AbstractStructuralDBElement $node2 */ $node2 = new $class(); $node2->setName('Node 2'); + $this->addReference($class . '_2', $node2); /** @var AbstractStructuralDBElement $node3 */ $node3 = new $class(); $node3->setName('Node 3'); + $this->addReference($class . '_3', $node3); $node1_1 = new $class(); $node1_1->setName('Node 1.1'); $node1_1->setParent($node1); + $this->addReference($class . '_4', $node1_1); $node1_2 = new $class(); $node1_2->setName('Node 1.2'); $node1_2->setParent($node1); + $this->addReference($class . '_5', $node1_2); $node2_1 = new $class(); $node2_1->setName('Node 2.1'); $node2_1->setParent($node2); + $this->addReference($class . '_6', $node2_1); $node1_1_1 = new $class(); $node1_1_1->setName('Node 1.1.1'); $node1_1_1->setParent($node1_1); + $this->addReference($class . '_7', $node1_1_1); $manager->persist($node1); $manager->persist($node2); diff --git a/src/DataFixtures/LogEntryFixtures.php b/src/DataFixtures/LogEntryFixtures.php new file mode 100644 index 00000000..b9788ec0 --- /dev/null +++ b/src/DataFixtures/LogEntryFixtures.php @@ -0,0 +1,106 @@ +. + */ + +declare(strict_types=1); + + +namespace App\DataFixtures; + +use App\Entity\LogSystem\ElementCreatedLogEntry; +use App\Entity\LogSystem\ElementDeletedLogEntry; +use App\Entity\LogSystem\ElementEditedLogEntry; +use App\Entity\Parts\Category; +use App\Entity\UserSystem\User; +use Doctrine\Bundle\FixturesBundle\Fixture; +use Doctrine\Common\DataFixtures\DependentFixtureInterface; +use Doctrine\Persistence\ObjectManager; + +class LogEntryFixtures extends Fixture implements DependentFixtureInterface +{ + + public function load(ObjectManager $manager) + { + $this->createCategoryEntries($manager); + $this->createDeletedCategory($manager); + } + + public function createCategoryEntries(ObjectManager $manager): void + { + $category = $this->getReference(Category::class . '_1', Category::class); + + $logEntry = new ElementCreatedLogEntry($category); + $logEntry->setTimestamp(new \DateTimeImmutable("+1 second")); + $logEntry->setUser($this->getReference(UserFixtures::ADMIN, User::class)); + $logEntry->setComment('Test'); + $manager->persist($logEntry); + + $logEntry = new ElementEditedLogEntry($category); + $logEntry->setTimestamp(new \DateTimeImmutable("+2 second")); + $logEntry->setUser($this->getReference(UserFixtures::ADMIN, User::class)); + $logEntry->setComment('Test'); + + $logEntry->setOldData(['name' => 'Test']); + $logEntry->setNewData(['name' => 'Node 1.1']); + + $manager->persist($logEntry); + $manager->flush(); + } + + public function createDeletedCategory(ObjectManager $manager): void + { + //We create a fictive category to test the deletion + $category = new Category(); + $category->setName('Node 100'); + + //Assume a category with id 100 was deleted + $reflClass = new \ReflectionClass($category); + $reflClass->getProperty('id')->setValue($category, 100); + + //The whole lifecycle from creation to deletion + $logEntry = new ElementCreatedLogEntry($category); + $logEntry->setUser($this->getReference(UserFixtures::ADMIN, User::class)); + $logEntry->setComment('Creation'); + $manager->persist($logEntry); + + $logEntry = new ElementEditedLogEntry($category); + $logEntry->setTimestamp(new \DateTimeImmutable("+1 second")); + $logEntry->setUser($this->getReference(UserFixtures::ADMIN, User::class)); + $logEntry->setComment('Edit'); + $logEntry->setOldData(['name' => 'Test']); + $logEntry->setNewData(['name' => 'Node 100']); + $manager->persist($logEntry); + + $logEntry = new ElementDeletedLogEntry($category); + $logEntry->setTimestamp(new \DateTimeImmutable("+2 second")); + $logEntry->setUser($this->getReference(UserFixtures::ADMIN, User::class)); + $logEntry->setOldData(['name' => 'Node 100', 'id' => 100, 'comment' => 'Test comment']); + $manager->persist($logEntry); + + $manager->flush(); + } + + public function getDependencies(): array + { + return [ + UserFixtures::class, + DataStructureFixtures::class + ]; + } +} \ No newline at end of file diff --git a/src/DataFixtures/PartFixtures.php b/src/DataFixtures/PartFixtures.php index a11b11e9..a9290cbf 100644 --- a/src/DataFixtures/PartFixtures.php +++ b/src/DataFixtures/PartFixtures.php @@ -73,6 +73,7 @@ class PartFixtures extends Fixture implements DependentFixtureInterface $part = new Part(); $part->setName('Part 1'); $part->setCategory($manager->find(Category::class, 1)); + $this->addReference(Part::class . '_1', $part); $manager->persist($part); /** More complex part */ @@ -86,6 +87,7 @@ class PartFixtures extends Fixture implements DependentFixtureInterface $part->setIpn('IPN123'); $part->setNeedsReview(true); $part->setManufacturingStatus(ManufacturingStatus::ACTIVE); + $this->addReference(Part::class . '_2', $part); $manager->persist($part); /** Part with orderdetails, storelocations and Attachments */ @@ -98,8 +100,9 @@ class PartFixtures extends Fixture implements DependentFixtureInterface $partLot1->setStorageLocation($manager->find(StorageLocation::class, 1)); $part->addPartLot($partLot1); + $partLot2 = new PartLot(); - $partLot2->setExpirationDate(new DateTime()); + $partLot2->setExpirationDate(new \DateTimeImmutable()); $partLot2->setComment('Test'); $partLot2->setNeedsRefill(true); $partLot2->setStorageLocation($manager->find(StorageLocation::class, 3)); @@ -133,6 +136,8 @@ class PartFixtures extends Fixture implements DependentFixtureInterface $attachment->setAttachmentType($manager->find(AttachmentType::class, 1)); $part->addAttachment($attachment); + $this->addReference(Part::class . '_3', $part); + $manager->persist($part); $manager->flush(); } diff --git a/src/DataTables/Adapters/TwoStepORMAdapter.php b/src/DataTables/Adapters/TwoStepORMAdapter.php index e225ce2f..2086c907 100644 --- a/src/DataTables/Adapters/TwoStepORMAdapter.php +++ b/src/DataTables/Adapters/TwoStepORMAdapter.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace App\DataTables\Adapters; +use Doctrine\ORM\Query\Expr\From; use Doctrine\ORM\Query; use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\Tools\Pagination\Paginator; @@ -51,12 +52,12 @@ class TwoStepORMAdapter extends ORMAdapter private bool $use_simple_total = false; - private \Closure|null $query_modifier; + private \Closure|null $query_modifier = null; public function __construct(ManagerRegistry $registry = null) { parent::__construct($registry); - $this->detailQueryCallable = static function (QueryBuilder $qb, array $ids) { + $this->detailQueryCallable = static function (QueryBuilder $qb, array $ids): never { throw new \RuntimeException('You need to set the detail_query option to use the TwoStepORMAdapter'); }; } @@ -66,9 +67,7 @@ class TwoStepORMAdapter extends ORMAdapter parent::configureOptions($resolver); $resolver->setRequired('filter_query'); - $resolver->setDefault('query', function (Options $options) { - return $options['filter_query']; - }); + $resolver->setDefault('query', fn(Options $options) => $options['filter_query']); $resolver->setRequired('detail_query'); $resolver->setAllowedTypes('detail_query', \Closure::class); @@ -108,7 +107,7 @@ class TwoStepORMAdapter extends ORMAdapter } } - /** @var Query\Expr\From $fromClause */ + /** @var From $fromClause */ $fromClause = $builder->getDQLPart('from')[0]; $identifier = "{$fromClause->getAlias()}.{$this->metadata->getSingleIdentifierFieldName()}"; @@ -201,7 +200,7 @@ class TwoStepORMAdapter extends ORMAdapter /** The paginator count queries can be rather slow, so when query for total count (100ms or longer), * just return the entity count. */ - /** @var Query\Expr\From $from_expr */ + /** @var From $from_expr */ $from_expr = $queryBuilder->getDQLPart('from')[0]; return $this->manager->getRepository($from_expr->getFrom())->count([]); diff --git a/src/DataTables/AttachmentDataTable.php b/src/DataTables/AttachmentDataTable.php index a53d61c9..0d6c5b53 100644 --- a/src/DataTables/AttachmentDataTable.php +++ b/src/DataTables/AttachmentDataTable.php @@ -34,6 +34,7 @@ use App\Services\EntityURLGenerator; use Doctrine\ORM\QueryBuilder; use Omines\DataTablesBundle\Adapter\Doctrine\ORM\SearchCriteriaProvider; use Omines\DataTablesBundle\Adapter\Doctrine\ORMAdapter; +use Omines\DataTablesBundle\Column\NumberColumn; use Omines\DataTablesBundle\Column\TextColumn; use Omines\DataTablesBundle\DataTable; use Omines\DataTablesBundle\DataTableTypeInterface; @@ -84,6 +85,11 @@ final class AttachmentDataTable implements DataTableTypeInterface }, ]); + $dataTable->add('id', NumberColumn::class, [ + 'label' => $this->translator->trans('part.table.id'), + 'visible' => false, + ]); + $dataTable->add('name', TextColumn::class, [ 'label' => 'attachment.edit.name', 'orderField' => 'NATSORT(attachment.name)', @@ -92,7 +98,7 @@ final class AttachmentDataTable implements DataTableTypeInterface if ($context->isExternal()) { return sprintf( '%s', - htmlspecialchars($context->getURL()), + htmlspecialchars((string) $context->getURL()), htmlspecialchars($value) ); } diff --git a/src/DataTables/Column/EnumColumn.php b/src/DataTables/Column/EnumColumn.php index e41b79e4..5a5d998d 100644 --- a/src/DataTables/Column/EnumColumn.php +++ b/src/DataTables/Column/EnumColumn.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Column; use Omines\DataTablesBundle\Column\AbstractColumn; diff --git a/src/DataTables/Column/LocaleDateTimeColumn.php b/src/DataTables/Column/LocaleDateTimeColumn.php index d485913c..e60b2867 100644 --- a/src/DataTables/Column/LocaleDateTimeColumn.php +++ b/src/DataTables/Column/LocaleDateTimeColumn.php @@ -47,7 +47,7 @@ class LocaleDateTimeColumn extends AbstractColumn } if (!$value instanceof DateTimeInterface) { - $value = new DateTime((string) $value); + $value = new \DateTimeImmutable((string) $value); } $formatValues = [ diff --git a/src/DataTables/Filters/Constraints/DateTimeConstraint.php b/src/DataTables/Filters/Constraints/DateTimeConstraint.php index 1ded472e..23134de5 100644 --- a/src/DataTables/Filters/Constraints/DateTimeConstraint.php +++ b/src/DataTables/Filters/Constraints/DateTimeConstraint.php @@ -22,9 +22,92 @@ declare(strict_types=1); */ namespace App\DataTables\Filters\Constraints; +use Doctrine\ORM\QueryBuilder; +use RuntimeException; + /** - * An alias of NumberConstraint to use to filter on a DateTime + * Similar to NumberConstraint but for DateTime values */ -class DateTimeConstraint extends NumberConstraint +class DateTimeConstraint extends AbstractConstraint { + protected const ALLOWED_OPERATOR_VALUES = ['=', '!=', '<', '>', '<=', '>=', 'BETWEEN']; + + public function __construct( + string $property, + string $identifier = null, + /** + * The value1 used for comparison (this is the main one used for all mono-value comparisons) + */ + protected \DateTimeInterface|null $value1 = null, + protected ?string $operator = null, + /** + * The second value used when operator is RANGE; this is the upper bound of the range + */ + protected \DateTimeInterface|null $value2 = null) + { + parent::__construct($property, $identifier); + } + + public function getValue1(): ?\DateTimeInterface + { + return $this->value1; + } + + public function setValue1(\DateTimeInterface|null $value1): void + { + $this->value1 = $value1; + } + + public function getValue2(): ?\DateTimeInterface + { + return $this->value2; + } + + public function setValue2(?\DateTimeInterface $value2): void + { + $this->value2 = $value2; + } + + public function getOperator(): string|null + { + return $this->operator; + } + + /** + * @param string $operator + */ + public function setOperator(?string $operator): void + { + $this->operator = $operator; + } + + public function isEnabled(): bool + { + return $this->value1 !== null + && ($this->operator !== null && $this->operator !== ''); + } + + public function apply(QueryBuilder $queryBuilder): void + { + //If no value is provided then we do not apply a filter + if (!$this->isEnabled()) { + return; + } + + //Ensure we have an valid operator + if(!in_array($this->operator, self::ALLOWED_OPERATOR_VALUES, true)) { + throw new \RuntimeException('Invalid operator '. $this->operator . ' provided. Valid operators are '. implode(', ', self::ALLOWED_OPERATOR_VALUES)); + } + + if ($this->operator !== 'BETWEEN') { + $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier, $this->operator, $this->value1); + } else { + if ($this->value2 === null) { + throw new RuntimeException("Cannot use operator BETWEEN without value2!"); + } + + $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier . '1', '>=', $this->value1); + $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier . '2', '<=', $this->value2); + } + } } diff --git a/src/DataTables/Filters/Constraints/EntityConstraint.php b/src/DataTables/Filters/Constraints/EntityConstraint.php index 671b743f..6e9721f4 100644 --- a/src/DataTables/Filters/Constraints/EntityConstraint.php +++ b/src/DataTables/Filters/Constraints/EntityConstraint.php @@ -137,7 +137,7 @@ class EntityConstraint extends AbstractConstraint } //We need to handle null values differently, as they can not be compared with == or != - if (!$this->value instanceof AbstractDBElement) { + if ($this->value === null) { if($this->operator === '=' || $this->operator === 'INCLUDING_CHILDREN') { $queryBuilder->andWhere(sprintf("%s IS NULL", $this->property)); return; @@ -152,8 +152,9 @@ class EntityConstraint extends AbstractConstraint } if($this->operator === '=' || $this->operator === '!=') { - $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier, $this->operator, $this->value); - return; + //Include null values on != operator, so that really all values are returned that are not equal to the given value + $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier, $this->operator, $this->value, $this->operator === '!='); + return; } //Otherwise retrieve the children list and apply the operator to it @@ -168,7 +169,8 @@ class EntityConstraint extends AbstractConstraint } if ($this->operator === 'EXCLUDING_CHILDREN') { - $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier, 'NOT IN', $list); + //Include null values in the result, so that all elements that are not in the list are returned + $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier, 'NOT IN', $list, true); return; } } else { diff --git a/src/DataTables/Filters/Constraints/FilterTrait.php b/src/DataTables/Filters/Constraints/FilterTrait.php index 26707841..3260e4e3 100644 --- a/src/DataTables/Filters/Constraints/FilterTrait.php +++ b/src/DataTables/Filters/Constraints/FilterTrait.php @@ -56,8 +56,14 @@ trait FilterTrait /** * Adds a simple constraint in the form of (property OPERATOR value) (e.g. "part.name = :name") to the given query builder. + * @param QueryBuilder $queryBuilder The query builder to add the constraint to + * @param string $property The property to compare + * @param string $parameterIdentifier The identifier for the parameter + * @param string $comparison_operator The comparison operator to use + * @param mixed $value The value to compare to + * @param bool $include_null If true, the result of this constraint will also include null values of this property (useful for exclusion filters) */ - protected function addSimpleAndConstraint(QueryBuilder $queryBuilder, string $property, string $parameterIdentifier, string $comparison_operator, mixed $value): void + protected function addSimpleAndConstraint(QueryBuilder $queryBuilder, string $property, string $parameterIdentifier, string $comparison_operator, mixed $value, bool $include_null = false): void { if ($comparison_operator === 'IN' || $comparison_operator === 'NOT IN') { $expression = sprintf("%s %s (:%s)", $property, $comparison_operator, $parameterIdentifier); @@ -65,6 +71,10 @@ trait FilterTrait $expression = sprintf("%s %s :%s", $property, $comparison_operator, $parameterIdentifier); } + if ($include_null) { + $expression = sprintf("(%s OR %s IS NULL)", $expression, $property); + } + if($this->useHaving || $this->isAggregateFunctionString($property)) { //If the property is an aggregate function, we have to use the "having" instead of the "where" $queryBuilder->andHaving($expression); } else { diff --git a/src/DataTables/Filters/Constraints/NumberConstraint.php b/src/DataTables/Filters/Constraints/NumberConstraint.php index 9e53b8f3..c872dade 100644 --- a/src/DataTables/Filters/Constraints/NumberConstraint.php +++ b/src/DataTables/Filters/Constraints/NumberConstraint.php @@ -29,12 +29,28 @@ class NumberConstraint extends AbstractConstraint { protected const ALLOWED_OPERATOR_VALUES = ['=', '!=', '<', '>', '<=', '>=', 'BETWEEN']; - public function getValue1(): float|int|null|\DateTimeInterface + public function __construct( + string $property, + string $identifier = null, + /** + * The value1 used for comparison (this is the main one used for all mono-value comparisons) + */ + protected float|int|null $value1 = null, + protected ?string $operator = null, + /** + * The second value used when operator is RANGE; this is the upper bound of the range + */ + protected float|int|null $value2 = null) + { + parent::__construct($property, $identifier); + } + + public function getValue1(): float|int|null { return $this->value1; } - public function setValue1(float|int|\DateTimeInterface|null $value1): void + public function setValue1(float|int|null $value1): void { $this->value1 = $value1; } @@ -63,22 +79,6 @@ class NumberConstraint extends AbstractConstraint } - public function __construct( - string $property, - string $identifier = null, - /** - * The value1 used for comparison (this is the main one used for all mono-value comparisons) - */ - protected float|int|\DateTimeInterface|null $value1 = null, - protected ?string $operator = null, - /** - * The second value used when operator is RANGE; this is the upper bound of the range - */ - protected float|int|\DateTimeInterface|null $value2 = null) - { - parent::__construct($property, $identifier); - } - public function isEnabled(): bool { return $this->value1 !== null @@ -105,7 +105,13 @@ class NumberConstraint extends AbstractConstraint } $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier . '1', '>=', $this->value1); - $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier . '2', '<=', $this->value2); + + //Workaround for the amountSum which we need to add twice on postgres. Replace one of the __ with __2 to make it work + //Otherwise we get an error, that __partLot was already defined + + $property2 = str_replace('__', '__2', $this->property); + + $this->addSimpleAndConstraint($queryBuilder, $property2, $this->identifier . '2', '<=', $this->value2); } } } diff --git a/src/DataTables/Filters/Constraints/Part/TagsConstraint.php b/src/DataTables/Filters/Constraints/Part/TagsConstraint.php index baae69c7..e8bfe2a4 100644 --- a/src/DataTables/Filters/Constraints/Part/TagsConstraint.php +++ b/src/DataTables/Filters/Constraints/Part/TagsConstraint.php @@ -30,16 +30,9 @@ class TagsConstraint extends AbstractConstraint { final public const ALLOWED_OPERATOR_VALUES = ['ANY', 'ALL', 'NONE']; - /** - * @param string $value - */ - public function __construct(string $property, string $identifier = null, /** - * @var string The value to compare to - */ - protected $value = null, /** - * @var string|null The operator to use - */ - protected ?string $operator = '') + public function __construct(string $property, string $identifier = null, + protected ?string $value = null, + protected ?string $operator = '') { parent::__construct($property, $identifier); } @@ -61,12 +54,12 @@ class TagsConstraint extends AbstractConstraint return $this; } - public function getValue(): string + public function getValue(): ?string { return $this->value; } - public function setValue(string $value): self + public function setValue(?string $value): self { $this->value = $value; return $this; @@ -92,6 +85,9 @@ class TagsConstraint extends AbstractConstraint */ protected function getExpressionForTag(QueryBuilder $queryBuilder, string $tag): Orx { + //Escape any %, _ or \ in the tag + $tag = addcslashes($tag, '%_\\'); + $tag_identifier_prefix = uniqid($this->identifier . '_', false); $expr = $queryBuilder->expr(); diff --git a/src/DataTables/Filters/Constraints/TextConstraint.php b/src/DataTables/Filters/Constraints/TextConstraint.php index 186a4477..89567d83 100644 --- a/src/DataTables/Filters/Constraints/TextConstraint.php +++ b/src/DataTables/Filters/Constraints/TextConstraint.php @@ -33,9 +33,9 @@ class TextConstraint extends AbstractConstraint * @param string $value */ public function __construct(string $property, string $identifier = null, /** - * @var string The value to compare to + * @var string|null The value to compare to */ - protected $value = null, /** + protected ?string $value = null, /** * @var string|null The operator to use */ protected ?string $operator = '') @@ -60,12 +60,12 @@ class TextConstraint extends AbstractConstraint return $this; } - public function getValue(): string + public function getValue(): ?string { return $this->value; } - public function setValue(string $value): self + public function setValue(?string $value): self { $this->value = $value; return $this; diff --git a/src/DataTables/Helpers/ColumnSortHelper.php b/src/DataTables/Helpers/ColumnSortHelper.php index c61ad6ce..05bd8182 100644 --- a/src/DataTables/Helpers/ColumnSortHelper.php +++ b/src/DataTables/Helpers/ColumnSortHelper.php @@ -109,7 +109,7 @@ class ColumnSortHelper } //and the remaining non-visible columns - foreach ($this->columns as $col_id => $col_data) { + foreach (array_keys($this->columns) as $col_id) { if (in_array($col_id, $processed_columns, true)) { // column already processed continue; diff --git a/src/DataTables/LogDataTable.php b/src/DataTables/LogDataTable.php index b14c1b1f..f6604279 100644 --- a/src/DataTables/LogDataTable.php +++ b/src/DataTables/LogDataTable.php @@ -162,7 +162,7 @@ class LogDataTable implements DataTableTypeInterface if (!$user instanceof User) { if ($context->isCLIEntry()) { return sprintf('%s [%s]', - htmlentities($context->getCLIUsername()), + htmlentities((string) $context->getCLIUsername()), $this->translator->trans('log.cli_user') ); } diff --git a/src/DataTables/PartsDataTable.php b/src/DataTables/PartsDataTable.php index 7f1df5e5..f62d9083 100644 --- a/src/DataTables/PartsDataTable.php +++ b/src/DataTables/PartsDataTable.php @@ -137,7 +137,8 @@ final class PartsDataTable implements DataTableTypeInterface ]) ->add('storelocation', TextColumn::class, [ 'label' => $this->translator->trans('part.table.storeLocations'), - 'orderField' => 'NATSORT(_storelocations.name)', + //We need to use a aggregate function to get the first store location, as we have a one-to-many relation + 'orderField' => 'NATSORT(MIN(_storelocations.name))', 'render' => fn ($value, Part $context) => $this->partDataTableHelper->renderStorageLocations($context), ], alias: 'storage_location') @@ -156,7 +157,7 @@ final class PartsDataTable implements DataTableTypeInterface 'orderField' => 'NATSORT(_partUnit.name)', 'render' => function($value, Part $context): string { $partUnit = $context->getPartUnit(); - if (!$partUnit) { + if ($partUnit === null) { return ''; } @@ -184,7 +185,7 @@ final class PartsDataTable implements DataTableTypeInterface 'label' => $this->translator->trans('part.table.manufacturingStatus'), 'class' => ManufacturingStatus::class, 'render' => function (?ManufacturingStatus $status, Part $context): string { - if (!$status) { + if ($status === null) { return ''; } diff --git a/src/DataTables/ProjectBomEntriesDataTable.php b/src/DataTables/ProjectBomEntriesDataTable.php index c59ca3bd..84a89320 100644 --- a/src/DataTables/ProjectBomEntriesDataTable.php +++ b/src/DataTables/ProjectBomEntriesDataTable.php @@ -85,7 +85,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface 'orderField' => 'NATSORT(part.name)', 'render' => function ($value, ProjectBOMEntry $context) { if(!$context->getPart() instanceof Part) { - return htmlspecialchars($context->getName()); + return htmlspecialchars((string) $context->getName()); } if($context->getPart() instanceof Part) { $tmp = $this->partDataTableHelper->renderName($context->getPart()); @@ -154,7 +154,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface 'label' => 'project.bom.instockAmount', 'visible' => false, 'render' => function ($value, ProjectBOMEntry $context) { - if ($context->getPart()) { + if ($context->getPart() !== null) { return $this->partDataTableHelper->renderAmount($context->getPart()); } @@ -165,7 +165,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface 'label' => 'part.table.storeLocations', 'visible' => false, 'render' => function ($value, ProjectBOMEntry $context) { - if ($context->getPart()) { + if ($context->getPart() !== null) { return $this->partDataTableHelper->renderStorageLocations($context->getPart()); } diff --git a/src/Doctrine/Functions/Field2.php b/src/Doctrine/Functions/Field2.php index 3b57fa27..57f55653 100644 --- a/src/Doctrine/Functions/Field2.php +++ b/src/Doctrine/Functions/Field2.php @@ -23,6 +23,8 @@ declare(strict_types=1); namespace App\Doctrine\Functions; +use Doctrine\ORM\Query\Parser; +use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Query\AST\Functions\FunctionNode; use Doctrine\ORM\Query\TokenType; @@ -36,7 +38,7 @@ class Field2 extends FunctionNode private $values = []; - public function parse(\Doctrine\ORM\Query\Parser $parser): void + public function parse(Parser $parser): void { $parser->match(TokenType::T_IDENTIFIER); $parser->match(TokenType::T_OPEN_PARENTHESIS); @@ -58,15 +60,16 @@ class Field2 extends FunctionNode $parser->match(TokenType::T_CLOSE_PARENTHESIS); } - public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker): string + public function getSql(SqlWalker $sqlWalker): string { $query = 'FIELD2('; $query .= $this->field->dispatch($sqlWalker); $query .= ', '; + $counter = count($this->values); - for ($i = 0; $i < count($this->values); $i++) { + for ($i = 0; $i < $counter; $i++) { if ($i > 0) { $query .= ', '; } @@ -74,8 +77,6 @@ class Field2 extends FunctionNode $query .= $this->values[$i]->dispatch($sqlWalker); } - $query .= ')'; - - return $query; + return $query . ')'; } } \ No newline at end of file diff --git a/src/Doctrine/Functions/Natsort.php b/src/Doctrine/Functions/Natsort.php index cf6fded9..bd05e0d6 100644 --- a/src/Doctrine/Functions/Natsort.php +++ b/src/Doctrine/Functions/Natsort.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace App\Doctrine\Functions; +use Doctrine\DBAL\Exception; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; @@ -54,27 +55,59 @@ class Natsort extends FunctionNode self::$allowSlowNaturalSort = $allow; } + /** + * Check if the slow natural sort is allowed + * @return bool + */ + public static function isSlowNaturalSortAllowed(): bool + { + return self::$allowSlowNaturalSort; + } + /** * Check if the MariaDB version which is connected to supports the natural sort (meaning it has a version of 10.7.0 or higher) * The result is cached in memory. * @param Connection $connection * @return bool - * @throws \Doctrine\DBAL\Exception + * @throws Exception */ - private static function mariaDBSupportsNaturalSort(Connection $connection): bool + private function mariaDBSupportsNaturalSort(Connection $connection): bool { if (self::$supportsNaturalSort !== null) { return self::$supportsNaturalSort; } $version = $connection->getServerVersion(); - //Remove the -MariaDB suffix - $version = str_replace('-MariaDB', '', $version); + + //Get the effective MariaDB version number + $version = $this->getMariaDbMysqlVersionNumber($version); + //We need at least MariaDB 10.7.0 to support the natural sort self::$supportsNaturalSort = version_compare($version, '10.7.0', '>='); return self::$supportsNaturalSort; } + /** + * Taken from Doctrine\DBAL\Driver\AbstractMySQLDriver + * + * Detect MariaDB server version, including hack for some mariadb distributions + * that starts with the prefix '5.5.5-' + * + * @param string $versionString Version string as returned by mariadb server, i.e. '5.5.5-Mariadb-10.0.8-xenial' + */ + private function getMariaDbMysqlVersionNumber(string $versionString) : string + { + if ( ! preg_match( + '/^(?:5\.5\.5-)?(mariadb-)?(?P\d+)\.(?P\d+)\.(?P\d+)/i', + $versionString, + $versionParts + )) { + throw new \RuntimeException('Could not detect MariaDB version from version string ' . $versionString); + } + + return $versionParts['major'] . '.' . $versionParts['minor'] . '.' . $versionParts['patch']; + } + public function parse(Parser $parser): void { $parser->match(TokenType::T_IDENTIFIER); @@ -95,7 +128,7 @@ class Natsort extends FunctionNode return $this->field->dispatch($sqlWalker) . ' COLLATE numeric'; } - if ($platform instanceof MariaDBPlatform && self::mariaDBSupportsNaturalSort($sqlWalker->getConnection())) { + if ($platform instanceof MariaDBPlatform && $this->mariaDBSupportsNaturalSort($sqlWalker->getConnection())) { return 'NATURAL_SORT_KEY(' . $this->field->dispatch($sqlWalker) . ')'; } diff --git a/src/Doctrine/Helpers/FieldHelper.php b/src/Doctrine/Helpers/FieldHelper.php index d49df4e3..11300db3 100644 --- a/src/Doctrine/Helpers/FieldHelper.php +++ b/src/Doctrine/Helpers/FieldHelper.php @@ -109,7 +109,7 @@ final class FieldHelper //If we are on MySQL, we can just use the FIELD function if ($db_platform instanceof AbstractMySQLPlatform) { $qb->orderBy("FIELD2($field_expr, :field_arr)", $order); - } else if ($db_platform instanceof PostgreSQLPlatform) { + } elseif ($db_platform instanceof PostgreSQLPlatform) { //Use the postgres native array_position function self::addPostgresOrderBy($qb, $field_expr, $key, $values, $order); } else { diff --git a/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareDriver.php b/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareDriver.php index c991d7b7..80a81612 100644 --- a/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareDriver.php +++ b/src/Doctrine/Middleware/SQLiteRegexExtensionMiddlewareDriver.php @@ -98,10 +98,9 @@ class SQLiteRegexExtensionMiddlewareDriver extends AbstractDriverMiddleware * This function returns the index (position) of the first argument in the subsequent arguments. * If the first argument is not found or is NULL, 0 is returned. * @param string|int|null $value - * @param mixed ...$array * @return int */ - final public static function field(string|int|null $value, ...$array): int + final public static function field(string|int|null $value, mixed ...$array): int { if ($value === null) { return 0; diff --git a/src/Doctrine/Types/UTCDateTimeImmutableType.php b/src/Doctrine/Types/UTCDateTimeImmutableType.php new file mode 100644 index 00000000..c0d6659d --- /dev/null +++ b/src/Doctrine/Types/UTCDateTimeImmutableType.php @@ -0,0 +1,97 @@ +. + */ + +declare(strict_types=1); + +namespace App\Doctrine\Types; + +use DateTime; +use DateTimeInterface; +use DateTimeZone; +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Types\ConversionException; +use Doctrine\DBAL\Types\DateTimeImmutableType; +use Doctrine\DBAL\Types\DateTimeType; +use Doctrine\DBAL\Types\Exception\InvalidFormat; + +/** + * This DateTimeImmutableType all dates to UTC, so it can be later used with the timezones. + * Taken (and adapted) from here: https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/cookbook/working-with-datetime.html. + */ +class UTCDateTimeImmutableType extends DateTimeImmutableType +{ + private static ?DateTimeZone $utc_timezone = null; + + /** + * {@inheritdoc} + * + * @param T $value + * + * @return (T is null ? null : string) + * + * @template T + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string + { + if (!self::$utc_timezone instanceof \DateTimeZone) { + self::$utc_timezone = new DateTimeZone('UTC'); + } + + if ($value instanceof \DateTimeImmutable) { + $value = $value->setTimezone(self::$utc_timezone); + } + + return parent::convertToDatabaseValue($value, $platform); + } + + /** + * {@inheritDoc} + * + * @param T $value + * + * @template T + */ + public function convertToPHPValue($value, AbstractPlatform $platform): ?\DateTimeImmutable + { + if (!self::$utc_timezone instanceof \DateTimeZone) { + self::$utc_timezone = new DateTimeZone('UTC'); + } + + if (null === $value || $value instanceof \DateTimeImmutable) { + return $value; + } + + $converted = \DateTimeImmutable::createFromFormat( + $platform->getDateTimeFormatString(), + $value, + self::$utc_timezone + ); + + if (!$converted) { + throw InvalidFormat::new( + $value, + static::class, + $platform->getDateTimeFormatString(), + ); + } + + return $converted; + } +} diff --git a/src/Entity/Attachments/Attachment.php b/src/Entity/Attachments/Attachment.php index a53a8d41..8dc01641 100644 --- a/src/Entity/Attachments/Attachment.php +++ b/src/Entity/Attachments/Attachment.php @@ -147,7 +147,7 @@ abstract class Attachment extends AbstractNamedDBElement * @var string|null the original filename the file had, when the user uploaded it */ #[ORM\Column(type: Types::STRING, nullable: true)] - #[Groups(['full', 'attachment:read'])] + #[Groups(['attachment:read', 'import'])] #[Assert\Length(max: 255)] protected ?string $original_filename = null; @@ -161,7 +161,7 @@ abstract class Attachment extends AbstractNamedDBElement * @var string the name of this element */ #[Assert\NotBlank(message: 'validator.attachment.name_not_blank')] - #[Groups(['simple', 'extended', 'full', 'attachment:read', 'attachment:write'])] + #[Groups(['simple', 'extended', 'full', 'attachment:read', 'attachment:write', 'import'])] protected string $name = ''; /** @@ -173,21 +173,21 @@ abstract class Attachment extends AbstractNamedDBElement protected ?AttachmentContainingDBElement $element = null; #[ORM\Column(type: Types::BOOLEAN)] - #[Groups(['attachment:read', 'attachment_write'])] + #[Groups(['attachment:read', 'attachment_write', 'full', 'import'])] 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'])] + #[Groups(['attachment:read', 'attachment:write', 'import', 'full'])] #[ApiProperty(readableLink: false)] protected ?AttachmentType $attachment_type = null; #[Groups(['attachment:read'])] - protected ?\DateTime $addedDate = null; + protected ?\DateTimeImmutable $addedDate = null; #[Groups(['attachment:read'])] - protected ?\DateTime $lastModified = null; + protected ?\DateTimeImmutable $lastModified = null; public function __construct() @@ -385,7 +385,7 @@ abstract class Attachment extends AbstractNamedDBElement return null; } - return parse_url($this->getURL(), PHP_URL_HOST); + return parse_url((string) $this->getURL(), PHP_URL_HOST); } /** @@ -477,7 +477,8 @@ abstract class Attachment extends AbstractNamedDBElement */ public function setElement(AttachmentContainingDBElement $element): self { - if (!is_a($element, static::ALLOWED_ELEMENT_CLASS)) { + //Do not allow Rector to replace this check with a instanceof. It will not work!! + if (!is_a($element, static::ALLOWED_ELEMENT_CLASS, true)) { throw new InvalidArgumentException(sprintf('The element associated with a %s must be a %s!', static::class, static::ALLOWED_ELEMENT_CLASS)); } diff --git a/src/Entity/Attachments/AttachmentContainingDBElement.php b/src/Entity/Attachments/AttachmentContainingDBElement.php index f2dfd3ef..3c863e85 100644 --- a/src/Entity/Attachments/AttachmentContainingDBElement.php +++ b/src/Entity/Attachments/AttachmentContainingDBElement.php @@ -45,7 +45,7 @@ abstract class AttachmentContainingDBElement extends AbstractNamedDBElement impl * @phpstan-var Collection * ORM Mapping is done in subclasses (e.g. Part) */ - #[Groups(['full'])] + #[Groups(['full', 'import'])] protected Collection $attachments; public function __construct() diff --git a/src/Entity/Attachments/AttachmentType.php b/src/Entity/Attachments/AttachmentType.php index 214f0af1..22333c16 100644 --- a/src/Entity/Attachments/AttachmentType.php +++ b/src/Entity/Attachments/AttachmentType.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Attachments; +use Doctrine\Common\Collections\Criteria; use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; @@ -86,7 +87,7 @@ use Symfony\Component\Validator\Constraints as Assert; class AttachmentType extends AbstractStructuralDBElement { #[ORM\OneToMany(mappedBy: 'parent', targetEntity: AttachmentType::class, cascade: ['persist'])] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; #[ORM\ManyToOne(targetEntity: AttachmentType::class, inversedBy: 'children')] @@ -102,7 +103,7 @@ class AttachmentType extends AbstractStructuralDBElement */ #[ORM\Column(type: Types::TEXT)] #[ValidFileFilter] - #[Groups(['attachment_type:read', 'attachment_type:write'])] + #[Groups(['attachment_type:read', 'attachment_type:write', 'import', 'extended'])] protected string $filetype_filter = ''; /** @@ -110,21 +111,21 @@ class AttachmentType extends AbstractStructuralDBElement */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: AttachmentTypeAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] - #[Groups(['attachment_type:read', 'attachment_type:write'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] + #[Groups(['attachment_type:read', 'attachment_type:write', 'import', 'full'])] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: AttachmentTypeAttachment::class)] #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] - #[Groups(['attachment_type:read', 'attachment_type:write'])] + #[Groups(['attachment_type:read', 'attachment_type:write', 'full'])] protected ?Attachment $master_picture_attachment = null; /** @var Collection */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: AttachmentTypeParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] - #[Groups(['attachment_type:read', 'attachment_type:write'])] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] + #[Groups(['attachment_type:read', 'attachment_type:write', 'import', 'full'])] protected Collection $parameters; /** @@ -134,9 +135,9 @@ class AttachmentType extends AbstractStructuralDBElement protected Collection $attachments_with_type; #[Groups(['attachment_type:read'])] - protected ?\DateTime $addedDate = null; + protected ?\DateTimeImmutable $addedDate = null; #[Groups(['attachment_type:read'])] - protected ?\DateTime $lastModified = null; + protected ?\DateTimeImmutable $lastModified = null; public function __construct() diff --git a/src/Entity/Base/AbstractCompany.php b/src/Entity/Base/AbstractCompany.php index 13e6010a..e3d4fd61 100644 --- a/src/Entity/Base/AbstractCompany.php +++ b/src/Entity/Base/AbstractCompany.php @@ -41,14 +41,14 @@ use Symfony\Component\Validator\Constraints as Assert; abstract class AbstractCompany extends AbstractPartsContainingDBElement { #[Groups(['company:read'])] - protected ?\DateTime $addedDate = null; + protected ?\DateTimeImmutable $addedDate = null; #[Groups(['company:read'])] - protected ?\DateTime $lastModified = null; + protected ?\DateTimeImmutable $lastModified = null; /** * @var string The address of the company */ - #[Groups(['full', 'company:read', 'company:write'])] + #[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])] #[ORM\Column(type: Types::STRING)] #[Assert\Length(max: 255)] protected string $address = ''; @@ -56,7 +56,7 @@ abstract class AbstractCompany extends AbstractPartsContainingDBElement /** * @var string The phone number of the company */ - #[Groups(['full', 'company:read', 'company:write'])] + #[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])] #[ORM\Column(type: Types::STRING)] #[Assert\Length(max: 255)] protected string $phone_number = ''; @@ -64,7 +64,7 @@ abstract class AbstractCompany extends AbstractPartsContainingDBElement /** * @var string The fax number of the company */ - #[Groups(['full', 'company:read', 'company:write'])] + #[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])] #[ORM\Column(type: Types::STRING)] #[Assert\Length(max: 255)] protected string $fax_number = ''; @@ -73,7 +73,7 @@ abstract class AbstractCompany extends AbstractPartsContainingDBElement * @var string The email address of the company */ #[Assert\Email] - #[Groups(['full', 'company:read', 'company:write'])] + #[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])] #[ORM\Column(type: Types::STRING)] #[Assert\Length(max: 255)] protected string $email_address = ''; @@ -82,12 +82,12 @@ abstract class AbstractCompany extends AbstractPartsContainingDBElement * @var string The website of the company */ #[Assert\Url] - #[Groups(['full', 'company:read', 'company:write'])] + #[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])] #[ORM\Column(type: Types::STRING)] #[Assert\Length(max: 255)] protected string $website = ''; - #[Groups(['company:read', 'company:write'])] + #[Groups(['company:read', 'company:write', 'import', 'full', 'extended'])] protected string $comment = ''; /** @@ -95,6 +95,7 @@ abstract class AbstractCompany extends AbstractPartsContainingDBElement */ #[ORM\Column(type: Types::STRING)] #[Assert\Length(max: 255)] + #[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])] protected string $auto_product_url = ''; /******************************************************************************** diff --git a/src/Entity/Base/AbstractPartsContainingDBElement.php b/src/Entity/Base/AbstractPartsContainingDBElement.php index 6ef09e34..e2209987 100644 --- a/src/Entity/Base/AbstractPartsContainingDBElement.php +++ b/src/Entity/Base/AbstractPartsContainingDBElement.php @@ -38,7 +38,7 @@ use Symfony\Component\Serializer\Annotation\Groups; #[ORM\MappedSuperclass(repositoryClass: AbstractPartsContainingRepository::class)] abstract class AbstractPartsContainingDBElement extends AbstractStructuralDBElement { - #[Groups(['full'])] + #[Groups(['full', 'import'])] protected Collection $parameters; public function __construct() diff --git a/src/Entity/Base/TimestampTrait.php b/src/Entity/Base/TimestampTrait.php index 94ddf236..77506b18 100644 --- a/src/Entity/Base/TimestampTrait.php +++ b/src/Entity/Base/TimestampTrait.php @@ -34,28 +34,28 @@ use Symfony\Component\Serializer\Annotation\Groups; trait TimestampTrait { /** - * @var \DateTime|null the date when this element was modified the last time + * @var \DateTimeImmutable|null the date when this element was modified the last time */ #[Groups(['extended', 'full'])] #[ApiProperty(writable: false)] - #[ORM\Column(name: 'last_modified', type: Types::DATETIME_MUTABLE, options: ['default' => 'CURRENT_TIMESTAMP'])] - protected ?\DateTime $lastModified = null; + #[ORM\Column(name: 'last_modified', type: Types::DATETIME_IMMUTABLE, options: ['default' => 'CURRENT_TIMESTAMP'])] + protected ?\DateTimeImmutable $lastModified = null; /** - * @var \DateTime|null the date when this element was created + * @var \DateTimeImmutable|null the date when this element was created */ #[Groups(['extended', 'full'])] #[ApiProperty(writable: false)] - #[ORM\Column(name: 'datetime_added', type: Types::DATETIME_MUTABLE, options: ['default' => 'CURRENT_TIMESTAMP'])] - protected ?\DateTime $addedDate = null; + #[ORM\Column(name: 'datetime_added', type: Types::DATETIME_IMMUTABLE, options: ['default' => 'CURRENT_TIMESTAMP'])] + protected ?\DateTimeImmutable $addedDate = null; /** * Returns the last time when the element was modified. * Returns null if the element was not yet saved to DB yet. * - * @return \DateTimeInterface|null the time of the last edit + * @return \DateTimeImmutable|null the time of the last edit */ - public function getLastModified(): ?\DateTimeInterface + public function getLastModified(): ?\DateTimeImmutable { return $this->lastModified; } @@ -64,9 +64,9 @@ trait TimestampTrait * Returns the date/time when the element was created. * Returns null if the element was not yet saved to DB yet. * - * @return \DateTimeInterface|null the creation time of the part + * @return \DateTimeImmutable|null the creation time of the part */ - public function getAddedDate(): ?\DateTimeInterface + public function getAddedDate(): ?\DateTimeImmutable { return $this->addedDate; } @@ -78,9 +78,9 @@ trait TimestampTrait #[ORM\PreUpdate] public function updateTimestamps(): void { - $this->lastModified = new DateTime('now'); + $this->lastModified = new \DateTimeImmutable('now'); if (null === $this->addedDate) { - $this->addedDate = new DateTime('now'); + $this->addedDate = new \DateTimeImmutable('now'); } } } diff --git a/src/Entity/EDA/EDACategoryInfo.php b/src/Entity/EDA/EDACategoryInfo.php index 47ce5e28..0163dfb3 100644 --- a/src/Entity/EDA/EDACategoryInfo.php +++ b/src/Entity/EDA/EDACategoryInfo.php @@ -36,33 +36,33 @@ class EDACategoryInfo * @var string|null The reference prefix of the Part in the schematic. E.g. "R" for resistors, or "C" for capacitors. */ #[Column(type: Types::STRING, nullable: true)] - #[Groups(['full', 'category:read', 'category:write'])] + #[Groups(['full', 'category:read', 'category:write', 'import'])] #[Length(max: 255)] private ?string $reference_prefix = null; /** @var bool|null Visibility of this part to EDA software in trinary logic. True=Visible, False=Invisible, Null=Auto */ #[Column(name: 'invisible', type: Types::BOOLEAN, nullable: true)] //TODO: Rename column to visibility - #[Groups(['full', 'category:read', 'category:write'])] + #[Groups(['full', 'category:read', 'category:write', 'import'])] private ?bool $visibility = null; /** @var bool|null If this is set to true, then this part will be excluded from the BOM */ #[Column(type: Types::BOOLEAN, nullable: true)] - #[Groups(['full', 'category:read', 'category:write'])] + #[Groups(['full', 'category:read', 'category:write', 'import'])] private ?bool $exclude_from_bom = null; /** @var bool|null If this is set to true, then this part will be excluded from the board/the PCB */ #[Column(type: Types::BOOLEAN, nullable: true)] - #[Groups(['full', 'category:read', 'category:write'])] + #[Groups(['full', 'category:read', 'category:write', 'import'])] private ?bool $exclude_from_board = null; /** @var bool|null If this is set to true, then this part will be excluded in the simulation */ #[Column(type: Types::BOOLEAN, nullable: true)] - #[Groups(['full', 'category:read', 'category:write'])] + #[Groups(['full', 'category:read', 'category:write', 'import'])] private ?bool $exclude_from_sim = true; /** @var string|null The KiCAD schematic symbol, which should be used (the path to the library) */ #[Column(type: Types::STRING, nullable: true)] - #[Groups(['full', 'category:read', 'category:write'])] + #[Groups(['full', 'category:read', 'category:write', 'import'])] #[Length(max: 255)] private ?string $kicad_symbol = null; diff --git a/src/Entity/EDA/EDAFootprintInfo.php b/src/Entity/EDA/EDAFootprintInfo.php index b9b7a0e6..9c5ef1c1 100644 --- a/src/Entity/EDA/EDAFootprintInfo.php +++ b/src/Entity/EDA/EDAFootprintInfo.php @@ -34,7 +34,7 @@ class EDAFootprintInfo { /** @var string|null The KiCAD footprint, which should be used (the path to the library) */ #[Column(type: Types::STRING, nullable: true)] - #[Groups(['full', 'footprint:read', 'footprint:write'])] + #[Groups(['full', 'footprint:read', 'footprint:write', 'import'])] #[Length(max: 255)] private ?string $kicad_footprint = null; diff --git a/src/Entity/EDA/EDAPartInfo.php b/src/Entity/EDA/EDAPartInfo.php index 258ef891..b4fc3588 100644 --- a/src/Entity/EDA/EDAPartInfo.php +++ b/src/Entity/EDA/EDAPartInfo.php @@ -36,45 +36,45 @@ class EDAPartInfo * @var string|null The reference prefix of the Part in the schematic. E.g. "R" for resistors, or "C" for capacitors. */ #[Column(type: Types::STRING, nullable: true)] - #[Groups(['full', 'eda_info:read', 'eda_info:write'])] + #[Groups(['full', 'eda_info:read', 'eda_info:write', 'import'])] #[Length(max: 255)] private ?string $reference_prefix = null; /** @var string|null The value, which should be shown together with the part (e.g. 470 for a 470 Ohm resistor) */ #[Column(type: Types::STRING, nullable: true)] - #[Groups(['full', 'eda_info:read', 'eda_info:write'])] + #[Groups(['full', 'eda_info:read', 'eda_info:write', 'import'])] #[Length(max: 255)] private ?string $value = null; /** @var bool|null Visibility of this part to EDA software in trinary logic. True=Visible, False=Invisible, Null=Auto */ #[Column(name: 'invisible', type: Types::BOOLEAN, nullable: true)] //TODO: Rename column to visibility - #[Groups(['full', 'eda_info:read', 'eda_info:write'])] + #[Groups(['full', 'eda_info:read', 'eda_info:write', 'import'])] private ?bool $visibility = null; /** @var bool|null If this is set to true, then this part will be excluded from the BOM */ #[Column(type: Types::BOOLEAN, nullable: true)] - #[Groups(['full', 'eda_info:read', 'eda_info:write'])] + #[Groups(['full', 'eda_info:read', 'eda_info:write', 'import'])] private ?bool $exclude_from_bom = null; /** @var bool|null If this is set to true, then this part will be excluded from the board/the PCB */ #[Column(type: Types::BOOLEAN, nullable: true)] - #[Groups(['full', 'eda_info:read', 'eda_info:write'])] + #[Groups(['full', 'eda_info:read', 'eda_info:write', 'import'])] private ?bool $exclude_from_board = null; /** @var bool|null If this is set to true, then this part will be excluded in the simulation */ #[Column(type: Types::BOOLEAN, nullable: true)] - #[Groups(['full', 'eda_info:read', 'eda_info:write'])] + #[Groups(['full', 'eda_info:read', 'eda_info:write', 'import'])] private ?bool $exclude_from_sim = null; /** @var string|null The KiCAD schematic symbol, which should be used (the path to the library) */ #[Column(type: Types::STRING, nullable: true)] - #[Groups(['full', 'eda_info:read', 'eda_info:write'])] + #[Groups(['full', 'eda_info:read', 'eda_info:write', 'import'])] #[Length(max: 255)] private ?string $kicad_symbol = null; /** @var string|null The KiCAD footprint, which should be used (the path to the library) */ #[Column(type: Types::STRING, nullable: true)] - #[Groups(['full', 'eda_info:read', 'eda_info:write'])] + #[Groups(['full', 'eda_info:read', 'eda_info:write', 'import'])] #[Length(max: 255)] private ?string $kicad_footprint = null; diff --git a/src/Entity/LabelSystem/BarcodeType.php b/src/Entity/LabelSystem/BarcodeType.php index 0794b606..daf7d401 100644 --- a/src/Entity/LabelSystem/BarcodeType.php +++ b/src/Entity/LabelSystem/BarcodeType.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\LabelSystem; enum BarcodeType: string diff --git a/src/Entity/LabelSystem/LabelOptions.php b/src/Entity/LabelSystem/LabelOptions.php index d432df80..ee1a5414 100644 --- a/src/Entity/LabelSystem/LabelOptions.php +++ b/src/Entity/LabelSystem/LabelOptions.php @@ -43,6 +43,7 @@ namespace App\Entity\LabelSystem; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Attribute\Groups; use Symfony\Component\Validator\Constraints as Assert; #[ORM\Embeddable] @@ -53,6 +54,7 @@ class LabelOptions */ #[Assert\Positive] #[ORM\Column(type: Types::FLOAT)] + #[Groups(["extended", "full", "import"])] protected float $width = 50.0; /** @@ -60,38 +62,45 @@ class LabelOptions */ #[Assert\Positive] #[ORM\Column(type: Types::FLOAT)] + #[Groups(["extended", "full", "import"])] protected float $height = 30.0; /** * @var BarcodeType The type of the barcode that should be used in the label (e.g. 'qr') */ #[ORM\Column(type: Types::STRING, enumType: BarcodeType::class)] + #[Groups(["extended", "full", "import"])] protected BarcodeType $barcode_type = BarcodeType::NONE; /** * @var LabelPictureType What image should be shown along the label */ #[ORM\Column(type: Types::STRING, enumType: LabelPictureType::class)] + #[Groups(["extended", "full", "import"])] protected LabelPictureType $picture_type = LabelPictureType::NONE; #[ORM\Column(type: Types::STRING, enumType: LabelSupportedElement::class)] + #[Groups(["extended", "full", "import"])] protected LabelSupportedElement $supported_element = LabelSupportedElement::PART; /** * @var string any additional CSS for the label */ #[ORM\Column(type: Types::TEXT)] + #[Groups([ "full", "import"])] protected string $additional_css = ''; /** @var LabelProcessMode The mode that will be used to interpret the lines */ #[ORM\Column(name: 'lines_mode', type: Types::STRING, enumType: LabelProcessMode::class)] + #[Groups(["extended", "full", "import"])] protected LabelProcessMode $process_mode = LabelProcessMode::PLACEHOLDER; /** * @var string */ #[ORM\Column(type: Types::TEXT)] + #[Groups(["extended", "full", "import"])] protected string $lines = ''; public function getWidth(): float diff --git a/src/Entity/LabelSystem/LabelPictureType.php b/src/Entity/LabelSystem/LabelPictureType.php index c9183ca6..c1f90fe2 100644 --- a/src/Entity/LabelSystem/LabelPictureType.php +++ b/src/Entity/LabelSystem/LabelPictureType.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\LabelSystem; enum LabelPictureType: string @@ -34,4 +36,4 @@ enum LabelPictureType: string * Show the main attachment of the element on the label */ case MAIN_ATTACHMENT = 'main_attachment'; -} \ No newline at end of file +} diff --git a/src/Entity/LabelSystem/LabelProcessMode.php b/src/Entity/LabelSystem/LabelProcessMode.php index 76bf175f..d5967b49 100644 --- a/src/Entity/LabelSystem/LabelProcessMode.php +++ b/src/Entity/LabelSystem/LabelProcessMode.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\LabelSystem; enum LabelProcessMode: string @@ -26,4 +28,4 @@ enum LabelProcessMode: string case PLACEHOLDER = 'html'; /** Interpret the given lines as twig template */ case TWIG = 'twig'; -} \ No newline at end of file +} diff --git a/src/Entity/LabelSystem/LabelProfile.php b/src/Entity/LabelSystem/LabelProfile.php index 0a5150e7..d3616c34 100644 --- a/src/Entity/LabelSystem/LabelProfile.php +++ b/src/Entity/LabelSystem/LabelProfile.php @@ -41,6 +41,7 @@ declare(strict_types=1); namespace App\Entity\LabelSystem; +use Doctrine\Common\Collections\Criteria; use App\Entity\Attachments\Attachment; use App\Repository\LabelProfileRepository; use App\EntityListeners\TreeCacheInvalidationListener; @@ -51,6 +52,7 @@ use App\Entity\Attachments\LabelAttachment; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; +use Symfony\Component\Serializer\Attribute\Groups; use Symfony\Component\Validator\Constraints as Assert; /** @@ -66,7 +68,7 @@ class LabelProfile extends AttachmentContainingDBElement * @var Collection */ #[ORM\OneToMany(mappedBy: 'element', targetEntity: LabelAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: LabelAttachment::class)] @@ -78,6 +80,7 @@ class LabelProfile extends AttachmentContainingDBElement */ #[Assert\Valid] #[ORM\Embedded(class: 'LabelOptions')] + #[Groups(["extended", "full", "import"])] protected LabelOptions $options; /** @@ -90,6 +93,7 @@ class LabelProfile extends AttachmentContainingDBElement * @var bool determines, if this label profile should be shown in the dropdown quick menu */ #[ORM\Column(type: Types::BOOLEAN)] + #[Groups(["extended", "full", "import"])] protected bool $show_in_dropdown = true; public function __construct() diff --git a/src/Entity/LabelSystem/LabelSupportedElement.php b/src/Entity/LabelSystem/LabelSupportedElement.php index 5007de7f..2f9afa85 100644 --- a/src/Entity/LabelSystem/LabelSupportedElement.php +++ b/src/Entity/LabelSystem/LabelSupportedElement.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\LabelSystem; use App\Entity\Parts\Part; @@ -42,4 +44,4 @@ enum LabelSupportedElement: string self::STORELOCATION => StorageLocation::class, }; } -} \ No newline at end of file +} diff --git a/src/Entity/LogSystem/AbstractLogEntry.php b/src/Entity/LogSystem/AbstractLogEntry.php index cbccbbf0..aa795613 100644 --- a/src/Entity/LogSystem/AbstractLogEntry.php +++ b/src/Entity/LogSystem/AbstractLogEntry.php @@ -25,7 +25,7 @@ namespace App\Entity\LogSystem; use Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractDBElement; use App\Entity\UserSystem\User; -use DateTime; + use Doctrine\ORM\Mapping as ORM; use App\Repository\LogEntryRepository; @@ -55,10 +55,11 @@ abstract class AbstractLogEntry extends AbstractDBElement #[ORM\Column(type: Types::STRING)] protected string $username = ''; - /** @var \DateTime The datetime the event associated with this log entry has occured + /** + * @var \DateTimeImmutable The datetime the event associated with this log entry has occured */ - #[ORM\Column(name: 'datetime', type: Types::DATETIME_MUTABLE)] - protected \DateTime $timestamp; + #[ORM\Column(name: 'datetime', type: Types::DATETIME_IMMUTABLE)] + protected \DateTimeImmutable $timestamp; /** * @var LogLevel The priority level of the associated level. 0 is highest, 7 lowest @@ -89,7 +90,7 @@ abstract class AbstractLogEntry extends AbstractDBElement public function __construct() { - $this->timestamp = new DateTime(); + $this->timestamp = new \DateTimeImmutable(); } /** @@ -164,7 +165,7 @@ abstract class AbstractLogEntry extends AbstractDBElement /** * Returns the timestamp when the event that caused this log entry happened. */ - public function getTimestamp(): \DateTimeInterface + public function getTimestamp(): \DateTimeImmutable { return $this->timestamp; } @@ -174,7 +175,7 @@ abstract class AbstractLogEntry extends AbstractDBElement * * @return $this */ - public function setTimestamp(\DateTime $timestamp): self + public function setTimestamp(\DateTimeImmutable $timestamp): self { $this->timestamp = $timestamp; diff --git a/src/Entity/LogSystem/LogLevel.php b/src/Entity/LogSystem/LogLevel.php index fd0c678b..435c5468 100644 --- a/src/Entity/LogSystem/LogLevel.php +++ b/src/Entity/LogSystem/LogLevel.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\LogSystem; use Psr\Log\LogLevel as PSRLogLevel; diff --git a/src/Entity/LogSystem/LogTargetType.php b/src/Entity/LogSystem/LogTargetType.php index 6e413079..1c6e4f8c 100644 --- a/src/Entity/LogSystem/LogTargetType.php +++ b/src/Entity/LogSystem/LogTargetType.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\LogSystem; use App\Entity\Attachments\Attachment; @@ -120,7 +122,7 @@ enum LogTargetType: int } } - $elementClass = is_object($element) ? get_class($element) : $element; + $elementClass = is_object($element) ? $element::class : $element; //If no matching type was found, throw an exception throw new \InvalidArgumentException("The given class $elementClass is not a valid log target type."); } diff --git a/src/Entity/LogSystem/LogWithEventUndoTrait.php b/src/Entity/LogSystem/LogWithEventUndoTrait.php index 16568241..ed8629dc 100644 --- a/src/Entity/LogSystem/LogWithEventUndoTrait.php +++ b/src/Entity/LogSystem/LogWithEventUndoTrait.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\LogSystem; use App\Entity\Contracts\LogWithEventUndoInterface; @@ -48,4 +50,4 @@ trait LogWithEventUndoTrait $mode_int = $this->extra['um'] ?? 1; return EventUndoMode::fromExtraInt($mode_int); } -} \ No newline at end of file +} diff --git a/src/Entity/LogSystem/PartStockChangeType.php b/src/Entity/LogSystem/PartStockChangeType.php index 92c5073f..f69fe95f 100644 --- a/src/Entity/LogSystem/PartStockChangeType.php +++ b/src/Entity/LogSystem/PartStockChangeType.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\LogSystem; enum PartStockChangeType: string @@ -53,4 +55,4 @@ enum PartStockChangeType: string default => throw new \InvalidArgumentException("Invalid short type: $value"), }; } -} \ No newline at end of file +} diff --git a/src/Entity/LogSystem/PartStockChangedLogEntry.php b/src/Entity/LogSystem/PartStockChangedLogEntry.php index dd7b5234..1bac9e9f 100644 --- a/src/Entity/LogSystem/PartStockChangedLogEntry.php +++ b/src/Entity/LogSystem/PartStockChangedLogEntry.php @@ -52,8 +52,6 @@ class PartStockChangedLogEntry extends AbstractLogEntry $this->level = LogLevel::INFO; $this->setTargetElement($lot); - - $this->typeString = 'part_stock_changed'; $this->extra = array_merge($this->extra, [ 't' => $type->toExtraShortType(), 'o' => $old_stock, diff --git a/src/Entity/LogSystem/SecurityEventLogEntry.php b/src/Entity/LogSystem/SecurityEventLogEntry.php index ffcfd6a5..1b65ca0b 100644 --- a/src/Entity/LogSystem/SecurityEventLogEntry.php +++ b/src/Entity/LogSystem/SecurityEventLogEntry.php @@ -127,7 +127,7 @@ class SecurityEventLogEntry extends AbstractLogEntry * Sets the IP address used to log in the user. * * @param string $ip the IP address used to log in the user - * @param bool $anonymize Anonymize the IP address (remove last block) to be GPDR compliant + * @param bool $anonymize Anonymize the IP address (remove last block) to be GDPR compliant * * @return $this */ diff --git a/src/Entity/LogSystem/UserLoginLogEntry.php b/src/Entity/LogSystem/UserLoginLogEntry.php index c9e6bc21..db62eb01 100644 --- a/src/Entity/LogSystem/UserLoginLogEntry.php +++ b/src/Entity/LogSystem/UserLoginLogEntry.php @@ -52,7 +52,7 @@ class UserLoginLogEntry extends AbstractLogEntry * Sets the IP address used to log in the user. * * @param string $ip the IP address used to log in the user - * @param bool $anonymize Anonymize the IP address (remove last block) to be GPDR compliant + * @param bool $anonymize Anonymize the IP address (remove last block) to be GDPR compliant * * @return $this */ diff --git a/src/Entity/LogSystem/UserLogoutLogEntry.php b/src/Entity/LogSystem/UserLogoutLogEntry.php index ba52de87..275b1d4e 100644 --- a/src/Entity/LogSystem/UserLogoutLogEntry.php +++ b/src/Entity/LogSystem/UserLogoutLogEntry.php @@ -49,7 +49,7 @@ class UserLogoutLogEntry extends AbstractLogEntry * Sets the IP address used to log in the user. * * @param string $ip the IP address used to log in the user - * @param bool $anonymize Anonymize the IP address (remove last block) to be GPDR compliant + * @param bool $anonymize Anonymize the IP address (remove last block) to be GDPR compliant * * @return $this */ diff --git a/src/Entity/OAuthToken.php b/src/Entity/OAuthToken.php index 2ecf9342..0073aeed 100644 --- a/src/Entity/OAuthToken.php +++ b/src/Entity/OAuthToken.php @@ -38,7 +38,7 @@ use League\OAuth2\Client\Token\AccessTokenInterface; class OAuthToken extends AbstractNamedDBElement implements AccessTokenInterface { /** @var string|null The short-term usable OAuth2 token */ - #[ORM\Column(type: 'text', nullable: true)] + #[ORM\Column(type: Types::TEXT, nullable: true)] private ?string $token = null; /** @var \DateTimeImmutable|null The date when the token expires */ @@ -46,7 +46,7 @@ class OAuthToken extends AbstractNamedDBElement implements AccessTokenInterface private ?\DateTimeImmutable $expires_at = null; /** @var string|null The refresh token for the OAuth2 auth */ - #[ORM\Column(type: 'text', nullable: true)] + #[ORM\Column(type: Types::TEXT, nullable: true)] private ?string $refresh_token = null; /** @@ -92,7 +92,7 @@ class OAuthToken extends AbstractNamedDBElement implements AccessTokenInterface return $this->token; } - public function getExpirationDate(): ?\DateTimeInterface + public function getExpirationDate(): ?\DateTimeImmutable { return $this->expires_at; } diff --git a/src/Entity/Parameters/AbstractParameter.php b/src/Entity/Parameters/AbstractParameter.php index 006ac302..c42682f9 100644 --- a/src/Entity/Parameters/AbstractParameter.php +++ b/src/Entity/Parameters/AbstractParameter.php @@ -116,7 +116,7 @@ abstract class AbstractParameter extends AbstractNamedDBElement implements Uniqu * @var string The mathematical symbol for this specification. Can be rendered pretty later. Should be short */ #[Assert\Length(max: 20)] - #[Groups(['full', 'parameter:read', 'parameter:write'])] + #[Groups(['full', 'parameter:read', 'parameter:write', 'import'])] #[ORM\Column(type: Types::STRING)] protected string $symbol = ''; @@ -126,7 +126,7 @@ abstract class AbstractParameter extends AbstractNamedDBElement implements Uniqu #[Assert\Type(['float', null])] #[Assert\LessThanOrEqual(propertyPath: 'value_typical', message: 'parameters.validator.min_lesser_typical')] #[Assert\LessThan(propertyPath: 'value_max', message: 'parameters.validator.min_lesser_max')] - #[Groups(['full', 'parameter:read', 'parameter:write'])] + #[Groups(['full', 'parameter:read', 'parameter:write', 'import'])] #[ORM\Column(type: Types::FLOAT, nullable: true)] protected ?float $value_min = null; @@ -134,7 +134,7 @@ abstract class AbstractParameter extends AbstractNamedDBElement implements Uniqu * @var float|null the typical value of this property */ #[Assert\Type([null, 'float'])] - #[Groups(['full', 'parameter:read', 'parameter:write'])] + #[Groups(['full', 'parameter:read', 'parameter:write', 'import'])] #[ORM\Column(type: Types::FLOAT, nullable: true)] protected ?float $value_typical = null; @@ -143,14 +143,14 @@ abstract class AbstractParameter extends AbstractNamedDBElement implements Uniqu */ #[Assert\Type(['float', null])] #[Assert\GreaterThanOrEqual(propertyPath: 'value_typical', message: 'parameters.validator.max_greater_typical')] - #[Groups(['full', 'parameter:read', 'parameter:write'])] + #[Groups(['full', 'parameter:read', 'parameter:write', 'import'])] #[ORM\Column(type: Types::FLOAT, nullable: true)] protected ?float $value_max = null; /** * @var string The unit in which the value values are given (e.g. V) */ - #[Groups(['full', 'parameter:read', 'parameter:write'])] + #[Groups(['full', 'parameter:read', 'parameter:write', 'import'])] #[ORM\Column(type: Types::STRING)] #[Assert\Length(max: 50)] protected string $unit = ''; @@ -158,7 +158,7 @@ abstract class AbstractParameter extends AbstractNamedDBElement implements Uniqu /** * @var string a text value for the given property */ - #[Groups(['full', 'parameter:read', 'parameter:write'])] + #[Groups(['full', 'parameter:read', 'parameter:write', 'import'])] #[ORM\Column(type: Types::STRING)] #[Assert\Length(max: 255)] protected string $value_text = ''; @@ -166,7 +166,7 @@ abstract class AbstractParameter extends AbstractNamedDBElement implements Uniqu /** * @var string the group this parameter belongs to */ - #[Groups(['full', 'parameter:read', 'parameter:write'])] + #[Groups(['full', 'parameter:read', 'parameter:write', 'import'])] #[ORM\Column(name: 'param_group', type: Types::STRING)] #[Assert\Length(max: 255)] protected string $group = ''; diff --git a/src/Entity/Parts/Category.php b/src/Entity/Parts/Category.php index ecd609ce..99ed3c6d 100644 --- a/src/Entity/Parts/Category.php +++ b/src/Entity/Parts/Category.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Parts; +use Doctrine\Common\Collections\Criteria; use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; @@ -91,7 +92,7 @@ use Symfony\Component\Validator\Constraints as Assert; class Category extends AbstractPartsContainingDBElement { #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] @@ -165,7 +166,7 @@ class Category extends AbstractPartsContainingDBElement #[Assert\Valid] #[Groups(['full', 'category:read', 'category:write'])] #[ORM\OneToMany(mappedBy: 'element', targetEntity: CategoryAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: CategoryAttachment::class)] @@ -178,13 +179,13 @@ class Category extends AbstractPartsContainingDBElement #[Assert\Valid] #[Groups(['full', 'category:read', 'category:write'])] #[ORM\OneToMany(mappedBy: 'element', targetEntity: CategoryParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] protected Collection $parameters; #[Groups(['category:read'])] - protected ?\DateTime $addedDate = null; + protected ?\DateTimeImmutable $addedDate = null; #[Groups(['category:read'])] - protected ?\DateTime $lastModified = null; + protected ?\DateTimeImmutable $lastModified = null; #[Assert\Valid] #[ORM\Embedded(class: EDACategoryInfo::class)] diff --git a/src/Entity/Parts/Footprint.php b/src/Entity/Parts/Footprint.php index 02c93694..6b043562 100644 --- a/src/Entity/Parts/Footprint.php +++ b/src/Entity/Parts/Footprint.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Parts; +use Doctrine\Common\Collections\Criteria; use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; @@ -96,7 +97,7 @@ class Footprint extends AbstractPartsContainingDBElement protected ?AbstractStructuralDBElement $parent = null; #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; #[Groups(['footprint:read', 'footprint:write'])] @@ -107,7 +108,7 @@ class Footprint extends AbstractPartsContainingDBElement */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: FootprintAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] #[Groups(['footprint:read', 'footprint:write'])] protected Collection $attachments; @@ -128,14 +129,14 @@ class Footprint extends AbstractPartsContainingDBElement */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: FootprintParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] #[Groups(['footprint:read', 'footprint:write'])] protected Collection $parameters; #[Groups(['footprint:read'])] - protected ?\DateTime $addedDate = null; + protected ?\DateTimeImmutable $addedDate = null; #[Groups(['footprint:read'])] - protected ?\DateTime $lastModified = null; + protected ?\DateTimeImmutable $lastModified = null; #[Assert\Valid] #[ORM\Embedded(class: EDAFootprintInfo::class)] diff --git a/src/Entity/Parts/InfoProviderReference.php b/src/Entity/Parts/InfoProviderReference.php index 305c1d9b..bfa62f32 100644 --- a/src/Entity/Parts/InfoProviderReference.php +++ b/src/Entity/Parts/InfoProviderReference.php @@ -31,31 +31,32 @@ use Symfony\Component\Serializer\Annotation\Groups; /** * This class represents a reference to a info provider inside a part. + * @see \App\Tests\Entity\Parts\InfoProviderReferenceTest */ #[Embeddable] class InfoProviderReference { /** @var string|null The key referencing the provider used to get this part, or null if it was not provided by a data provider */ - #[Column(type: 'string', nullable: true)] - #[Groups(['provider_reference:read'])] + #[Column(type: Types::STRING, nullable: true)] + #[Groups(['provider_reference:read', 'full'])] 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'])] + #[Column(type: Types::STRING, nullable: true)] + #[Groups(['provider_reference:read', 'full'])] 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'])] + #[Column(type: Types::STRING, nullable: true)] + #[Groups(['provider_reference:read', 'full'])] private ?string $provider_url = null; - #[Column(type: Types::DATETIME_MUTABLE, nullable: true, options: ['default' => null])] - #[Groups(['provider_reference:read'])] - private ?\DateTime $last_updated = null; + #[Column(type: Types::DATETIME_IMMUTABLE, nullable: true, options: ['default' => null])] + #[Groups(['provider_reference:read', 'full'])] + private ?\DateTimeImmutable $last_updated = null; /** * Constructing is forbidden from outside. @@ -94,9 +95,8 @@ class InfoProviderReference /** * Gets the time, when the part was last time updated by the provider. - * @return \DateTimeInterface|null */ - public function getLastUpdated(): ?\DateTimeInterface + public function getLastUpdated(): ?\DateTimeImmutable { return $this->last_updated; } @@ -139,7 +139,7 @@ class InfoProviderReference $ref->provider_key = $provider_key; $ref->provider_id = $provider_id; $ref->provider_url = $provider_url; - $ref->last_updated = new \DateTime(); + $ref->last_updated = new \DateTimeImmutable(); return $ref; } @@ -154,7 +154,7 @@ class InfoProviderReference $ref->provider_key = $dto->provider_key; $ref->provider_id = $dto->provider_id; $ref->provider_url = $dto->provider_url; - $ref->last_updated = new \DateTime(); + $ref->last_updated = new \DateTimeImmutable(); return $ref; } } \ No newline at end of file diff --git a/src/Entity/Parts/Manufacturer.php b/src/Entity/Parts/Manufacturer.php index 94f83e68..0edf8232 100644 --- a/src/Entity/Parts/Manufacturer.php +++ b/src/Entity/Parts/Manufacturer.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Parts; +use Doctrine\Common\Collections\Criteria; use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; @@ -95,7 +96,7 @@ class Manufacturer extends AbstractCompany protected ?AbstractStructuralDBElement $parent = null; #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; /** @@ -103,7 +104,7 @@ class Manufacturer extends AbstractCompany */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: ManufacturerAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] #[Groups(['manufacturer:read', 'manufacturer:write'])] #[ApiProperty(readableLink: false, writableLink: true)] protected Collection $attachments; @@ -118,7 +119,7 @@ class Manufacturer extends AbstractCompany */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: ManufacturerParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] #[Groups(['manufacturer:read', 'manufacturer:write'])] #[ApiProperty(readableLink: false, writableLink: true)] protected Collection $parameters; diff --git a/src/Entity/Parts/MeasurementUnit.php b/src/Entity/Parts/MeasurementUnit.php index 870ad374..6dd0b9f2 100644 --- a/src/Entity/Parts/MeasurementUnit.php +++ b/src/Entity/Parts/MeasurementUnit.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Parts; +use Doctrine\Common\Collections\Criteria; use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; @@ -98,7 +99,7 @@ class MeasurementUnit extends AbstractPartsContainingDBElement * or m (for meters). */ #[Assert\Length(max: 10)] - #[Groups(['extended', 'full', 'import', 'measurement_unit:read', 'measurement_unit:write'])] + #[Groups(['simple', 'extended', 'full', 'import', 'measurement_unit:read', 'measurement_unit:write'])] #[ORM\Column(name: 'unit', type: Types::STRING, nullable: true)] protected ?string $unit = null; @@ -109,7 +110,7 @@ class MeasurementUnit extends AbstractPartsContainingDBElement * @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', 'measurement_unit:read', 'measurement_unit:write'])] + #[Groups(['simple', 'extended', 'full', 'import', 'measurement_unit:read', 'measurement_unit:write'])] #[ORM\Column(name: 'is_integer', type: Types::BOOLEAN)] protected bool $is_integer = false; @@ -118,12 +119,12 @@ 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', 'measurement_unit:read', 'measurement_unit:write'])] + #[Groups(['simple', 'full', 'import', 'measurement_unit:read', 'measurement_unit:write'])] #[ORM\Column(name: 'use_si_prefix', type: Types::BOOLEAN)] protected bool $use_si_prefix = false; #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class, cascade: ['persist'])] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] @@ -137,7 +138,7 @@ class MeasurementUnit extends AbstractPartsContainingDBElement */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: MeasurementUnitAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] #[Groups(['measurement_unit:read', 'measurement_unit:write'])] protected Collection $attachments; @@ -150,14 +151,14 @@ class MeasurementUnit extends AbstractPartsContainingDBElement */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: MeasurementUnitParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] #[Groups(['measurement_unit:read', 'measurement_unit:write'])] protected Collection $parameters; #[Groups(['measurement_unit:read'])] - protected ?\DateTime $addedDate = null; + protected ?\DateTimeImmutable $addedDate = null; #[Groups(['measurement_unit:read'])] - protected ?\DateTime $lastModified = null; + protected ?\DateTimeImmutable $lastModified = null; /** diff --git a/src/Entity/Parts/Part.php b/src/Entity/Parts/Part.php index 090eda8b..14a7903f 100644 --- a/src/Entity/Parts/Part.php +++ b/src/Entity/Parts/Part.php @@ -22,6 +22,8 @@ declare(strict_types=1); namespace App\Entity\Parts; +use App\ApiPlatform\Filter\TagFilter; +use Doctrine\Common\Collections\Criteria; use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; @@ -96,7 +98,8 @@ 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(LikeFilter::class, properties: ["name", "comment", "description", "ipn", "manufacturer_product_number"])] +#[ApiFilter(TagFilter::class, properties: ["tags"])] #[ApiFilter(BooleanFilter::class, properties: ["favorite" , "needs_review"])] #[ApiFilter(RangeFilter::class, properties: ["mass", "minamount"])] #[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)] @@ -117,9 +120,9 @@ class Part extends AttachmentContainingDBElement /** @var Collection */ #[Assert\Valid] - #[Groups(['full', 'part:read', 'part:write'])] + #[Groups(['full', 'part:read', 'part:write', 'import'])] #[ORM\OneToMany(mappedBy: 'element', targetEntity: PartParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] #[UniqueObjectCollection(fields: ['name', 'group', 'element'])] protected Collection $parameters; @@ -140,7 +143,7 @@ class Part extends AttachmentContainingDBElement #[Assert\Valid] #[Groups(['full', 'part:read', 'part:write'])] #[ORM\OneToMany(mappedBy: 'element', targetEntity: PartAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $attachments; /** @@ -153,9 +156,9 @@ class Part extends AttachmentContainingDBElement protected ?Attachment $master_picture_attachment = null; #[Groups(['part:read'])] - protected ?\DateTime $addedDate = null; + protected ?\DateTimeImmutable $addedDate = null; #[Groups(['part:read'])] - protected ?\DateTime $lastModified = null; + protected ?\DateTimeImmutable $lastModified = null; public function __construct() diff --git a/src/Entity/Parts/PartAssociation.php b/src/Entity/Parts/PartAssociation.php index 843a63ee..32017488 100644 --- a/src/Entity/Parts/PartAssociation.php +++ b/src/Entity/Parts/PartAssociation.php @@ -49,6 +49,7 @@ use Symfony\Component\Validator\Constraints\Length; /** * This entity describes a part association, which is a semantic connection between two parts. * For example, a part association can be used to describe that a part is a replacement for another part. + * @see \App\Tests\Entity\Parts\PartAssociationTest */ #[ORM\Entity(repositoryClass: DBElementRepository::class)] #[ORM\HasLifecycleCallbacks] diff --git a/src/Entity/Parts/PartLot.php b/src/Entity/Parts/PartLot.php index 25b40d5a..70475efd 100644 --- a/src/Entity/Parts/PartLot.php +++ b/src/Entity/Parts/PartLot.php @@ -105,13 +105,13 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named protected string $comment = ''; /** - * @var \DateTime|null Set a time until when the lot must be used. + * @var \DateTimeImmutable|null Set a time until when the lot must be used. * Set to null, if the lot can be used indefinitely. */ #[Groups(['extended', 'full', 'import', 'part_lot:read', 'part_lot:write'])] - #[ORM\Column(name: 'expiration_date', type: Types::DATETIME_MUTABLE, nullable: true)] + #[ORM\Column(name: 'expiration_date', type: Types::DATETIME_IMMUTABLE, nullable: true)] #[Year2038BugWorkaround] - protected ?\DateTime $expiration_date = null; + protected ?\DateTimeImmutable $expiration_date = null; /** * @var StorageLocation|null The storelocation of this lot @@ -194,7 +194,7 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named } //Check if the expiration date is bigger then current time - return $this->expiration_date < new DateTime('now'); + return $this->expiration_date < new \DateTimeImmutable('now'); } /** @@ -236,7 +236,7 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named /** * Gets the expiration date for the part lot. Returns null, if no expiration date was set. */ - public function getExpirationDate(): ?\DateTimeInterface + public function getExpirationDate(): ?\DateTimeImmutable { return $this->expiration_date; } @@ -246,7 +246,7 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named * * */ - public function setExpirationDate(?\DateTime $expiration_date): self + public function setExpirationDate(?\DateTimeImmutable $expiration_date): self { $this->expiration_date = $expiration_date; diff --git a/src/Entity/Parts/PartTraits/AssociationTrait.php b/src/Entity/Parts/PartTraits/AssociationTrait.php index 402c8a3a..bb80fc5a 100644 --- a/src/Entity/Parts/PartTraits/AssociationTrait.php +++ b/src/Entity/Parts/PartTraits/AssociationTrait.php @@ -38,7 +38,7 @@ trait AssociationTrait #[Valid] #[ORM\OneToMany(mappedBy: 'owner', targetEntity: PartAssociation::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[Groups(['part:read', 'part:write'])] + #[Groups(['part:read', 'part:write', 'full'])] protected Collection $associated_parts_as_owner; /** diff --git a/src/Entity/Parts/PartTraits/EDATrait.php b/src/Entity/Parts/PartTraits/EDATrait.php index eafac58a..313552e7 100644 --- a/src/Entity/Parts/PartTraits/EDATrait.php +++ b/src/Entity/Parts/PartTraits/EDATrait.php @@ -32,7 +32,7 @@ trait EDATrait { #[Valid] #[Embedded(class: EDAPartInfo::class)] - #[Groups(['full', 'part:read', 'part:write'])] + #[Groups(['full', 'part:read', 'part:write', 'import'])] protected EDAPartInfo $eda_info; public function getEdaInfo(): EDAPartInfo diff --git a/src/Entity/Parts/PartTraits/InstockTrait.php b/src/Entity/Parts/PartTraits/InstockTrait.php index 5f73243d..08b070f3 100644 --- a/src/Entity/Parts/PartTraits/InstockTrait.php +++ b/src/Entity/Parts/PartTraits/InstockTrait.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Parts\PartTraits; +use Doctrine\Common\Collections\Criteria; use Doctrine\DBAL\Types\Types; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\PartLot; @@ -42,7 +43,7 @@ trait InstockTrait #[Assert\Valid] #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\OneToMany(mappedBy: 'part', targetEntity: PartLot::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['amount' => 'DESC'])] + #[ORM\OrderBy(['amount' => Criteria::DESC])] protected Collection $partLots; /** diff --git a/src/Entity/Parts/PartTraits/OrderTrait.php b/src/Entity/Parts/PartTraits/OrderTrait.php index 0a914e24..2c142016 100644 --- a/src/Entity/Parts/PartTraits/OrderTrait.php +++ b/src/Entity/Parts/PartTraits/OrderTrait.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Parts\PartTraits; +use Doctrine\Common\Collections\Criteria; use Doctrine\DBAL\Types\Types; use App\Entity\PriceInformations\Orderdetail; use Symfony\Component\Serializer\Annotation\Groups; @@ -41,7 +42,7 @@ trait OrderTrait #[Assert\Valid] #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\OneToMany(mappedBy: 'part', targetEntity: Orderdetail::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['supplierpartnr' => 'ASC'])] + #[ORM\OrderBy(['supplierpartnr' => Criteria::ASC])] protected Collection $orderdetails; /** diff --git a/src/Entity/Parts/StorageLocation.php b/src/Entity/Parts/StorageLocation.php index c88decac..6c455ae5 100644 --- a/src/Entity/Parts/StorageLocation.php +++ b/src/Entity/Parts/StorageLocation.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Parts; +use Doctrine\Common\Collections\Criteria; use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; @@ -90,7 +91,7 @@ use Symfony\Component\Validator\Constraints as Assert; class StorageLocation extends AbstractPartsContainingDBElement { #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] @@ -114,7 +115,7 @@ class StorageLocation extends AbstractPartsContainingDBElement */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: StorageLocationParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] #[Groups(['location:read', 'location:write'])] protected Collection $parameters; @@ -169,9 +170,9 @@ class StorageLocation extends AbstractPartsContainingDBElement protected ?Attachment $master_picture_attachment = null; #[Groups(['location:read'])] - protected ?\DateTime $addedDate = null; + protected ?\DateTimeImmutable $addedDate = null; #[Groups(['location:read'])] - protected ?\DateTime $lastModified = null; + protected ?\DateTimeImmutable $lastModified = null; /******************************************************************************** diff --git a/src/Entity/Parts/Supplier.php b/src/Entity/Parts/Supplier.php index 12373593..6b01a92c 100644 --- a/src/Entity/Parts/Supplier.php +++ b/src/Entity/Parts/Supplier.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Parts; +use Doctrine\Common\Collections\Criteria; use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; @@ -92,7 +93,7 @@ use Symfony\Component\Validator\Constraints as Assert; class Supplier extends AbstractCompany { #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] @@ -129,7 +130,7 @@ class Supplier extends AbstractCompany */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: SupplierAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] #[Groups(['supplier:read', 'supplier:write'])] #[ApiProperty(readableLink: false, writableLink: true)] protected Collection $attachments; @@ -144,7 +145,7 @@ class Supplier extends AbstractCompany */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: SupplierParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] #[Groups(['supplier:read', 'supplier:write'])] #[ApiProperty(readableLink: false, writableLink: true)] protected Collection $parameters; diff --git a/src/Entity/PriceInformations/Currency.php b/src/Entity/PriceInformations/Currency.php index 4e9a9acc..ce20caf8 100644 --- a/src/Entity/PriceInformations/Currency.php +++ b/src/Entity/PriceInformations/Currency.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\PriceInformations; +use Doctrine\Common\Collections\Criteria; use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; @@ -101,7 +102,7 @@ class Currency extends AbstractStructuralDBElement */ #[ORM\Column(type: 'big_decimal', precision: 11, scale: 5, nullable: true)] #[BigDecimalPositive] - #[Groups(['currency:read', 'currency:write'])] + #[Groups(['currency:read', 'currency:write', 'simple', 'extended', 'full', 'import'])] #[ApiProperty(readableLink: false, writableLink: false)] protected ?BigDecimal $exchange_rate = null; @@ -113,12 +114,12 @@ class Currency extends AbstractStructuralDBElement */ #[Assert\Currency] #[Assert\NotBlank] - #[Groups(['extended', 'full', 'import', 'currency:read', 'currency:write'])] + #[Groups(['simple', 'extended', 'full', 'import', 'currency:read', 'currency:write'])] #[ORM\Column(type: Types::STRING)] protected string $iso_code = ""; #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class, cascade: ['persist'])] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] @@ -132,7 +133,7 @@ class Currency extends AbstractStructuralDBElement */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: CurrencyAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] #[Groups(['currency:read', 'currency:write'])] protected Collection $attachments; @@ -145,7 +146,7 @@ class Currency extends AbstractStructuralDBElement */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: CurrencyParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] #[Groups(['currency:read', 'currency:write'])] protected Collection $parameters; @@ -155,9 +156,9 @@ class Currency extends AbstractStructuralDBElement protected Collection $pricedetails; #[Groups(['currency:read'])] - protected ?\DateTime $addedDate = null; + protected ?\DateTimeImmutable $addedDate = null; #[Groups(['currency:read'])] - protected ?\DateTime $lastModified = null; + protected ?\DateTimeImmutable $lastModified = null; public function __construct() diff --git a/src/Entity/PriceInformations/Orderdetail.php b/src/Entity/PriceInformations/Orderdetail.php index 811df111..3709b37d 100644 --- a/src/Entity/PriceInformations/Orderdetail.php +++ b/src/Entity/PriceInformations/Orderdetail.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace App\Entity\PriceInformations; +use Doctrine\Common\Collections\Criteria; use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; @@ -45,7 +46,7 @@ use App\Entity\Contracts\NamedElementInterface; use App\Entity\Contracts\TimeStampableInterface; use App\Entity\Parts\Part; use App\Entity\Parts\Supplier; -use DateTime; +use DateTimeImmutable; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; @@ -96,10 +97,13 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N { use TimestampTrait; + /** + * @var Collection + */ #[Assert\Valid] #[Groups(['extended', 'full', 'import', 'orderdetail:read', 'orderdetail:write'])] #[ORM\OneToMany(mappedBy: 'orderdetail', targetEntity: Pricedetail::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['min_discount_quantity' => 'ASC'])] + #[ORM\OrderBy(['min_discount_quantity' => Criteria::ASC])] protected Collection $pricedetails; /** @@ -169,9 +173,9 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N #[ORM\PreUpdate] public function updateTimestamps(): void { - $this->lastModified = new DateTime('now'); + $this->lastModified = new DateTimeImmutable('now'); if (!$this->addedDate instanceof \DateTimeInterface) { - $this->addedDate = new DateTime('now'); + $this->addedDate = new DateTimeImmutable('now'); } if ($this->part instanceof Part) { diff --git a/src/Entity/PriceInformations/Pricedetail.php b/src/Entity/PriceInformations/Pricedetail.php index 98d4a491..86a7bcd5 100644 --- a/src/Entity/PriceInformations/Pricedetail.php +++ b/src/Entity/PriceInformations/Pricedetail.php @@ -38,7 +38,7 @@ use App\Validator\Constraints\BigDecimal\BigDecimalPositive; use App\Validator\Constraints\Selectable; use Brick\Math\BigDecimal; use Brick\Math\RoundingMode; -use DateTime; +use DateTimeImmutable; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Serializer\Annotation\Groups; @@ -141,9 +141,9 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface #[ORM\PreUpdate] public function updateTimestamps(): void { - $this->lastModified = new DateTime('now'); + $this->lastModified = new DateTimeImmutable('now'); if (!$this->addedDate instanceof \DateTimeInterface) { - $this->addedDate = new DateTime('now'); + $this->addedDate = new DateTimeImmutable('now'); } if ($this->orderdetail instanceof Orderdetail) { diff --git a/src/Entity/ProjectSystem/Project.php b/src/Entity/ProjectSystem/Project.php index e537984f..1d1ed413 100644 --- a/src/Entity/ProjectSystem/Project.php +++ b/src/Entity/ProjectSystem/Project.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\ProjectSystem; +use Doctrine\Common\Collections\Criteria; use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiProperty; @@ -88,7 +89,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; class Project extends AbstractStructuralDBElement { #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] @@ -100,8 +101,11 @@ class Project extends AbstractStructuralDBElement #[Groups(['project:read', 'project:write'])] protected string $comment = ''; + /** + * @var Collection + */ #[Assert\Valid] - #[Groups(['extended', 'full'])] + #[Groups(['extended', 'full', 'import'])] #[ORM\OneToMany(mappedBy: 'project', targetEntity: ProjectBOMEntry::class, cascade: ['persist', 'remove'], orphanRemoval: true)] #[UniqueObjectCollection(message: 'project.bom_entry.part_already_in_bom', fields: ['part'])] #[UniqueObjectCollection(message: 'project.bom_entry.name_already_in_bom', fields: ['name'])] @@ -114,7 +118,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', 'project:read', 'project:write'])] + #[Groups(['extended', 'full', 'project:read', 'project:write', 'import'])] #[ORM\Column(type: Types::STRING, length: 64, nullable: true)] protected ?string $status = null; @@ -137,7 +141,7 @@ class Project extends AbstractStructuralDBElement * @var Collection */ #[ORM\OneToMany(mappedBy: 'element', targetEntity: ProjectAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] #[Groups(['project:read', 'project:write'])] protected Collection $attachments; @@ -149,14 +153,14 @@ class Project extends AbstractStructuralDBElement /** @var Collection */ #[ORM\OneToMany(mappedBy: 'element', targetEntity: ProjectParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] #[Groups(['project:read', 'project:write'])] protected Collection $parameters; #[Groups(['project:read'])] - protected ?\DateTime $addedDate = null; + protected ?\DateTimeImmutable $addedDate = null; #[Groups(['project:read'])] - protected ?\DateTime $lastModified = null; + protected ?\DateTimeImmutable $lastModified = null; /******************************************************************************** diff --git a/src/Entity/ProjectSystem/ProjectBOMEntry.php b/src/Entity/ProjectSystem/ProjectBOMEntry.php index 5b829be0..2a7862ec 100644 --- a/src/Entity/ProjectSystem/ProjectBOMEntry.php +++ b/src/Entity/ProjectSystem/ProjectBOMEntry.php @@ -90,14 +90,14 @@ class ProjectBOMEntry extends AbstractDBElement implements UniqueValidatableInte #[Assert\Positive] #[ORM\Column(name: 'quantity', type: Types::FLOAT)] - #[Groups(['bom_entry:read', 'bom_entry:write'])] + #[Groups(['bom_entry:read', 'bom_entry:write', 'import', 'simple', 'extended', 'full'])] protected float $quantity = 1.0; /** * @var string A comma separated list of the names, where this parts should be placed */ #[ORM\Column(name: 'mountnames', type: Types::TEXT)] - #[Groups(['bom_entry:read', 'bom_entry:write'])] + #[Groups(['bom_entry:read', 'bom_entry:write', 'import', 'simple', 'extended', 'full'])] protected string $mountnames = ''; /** @@ -105,14 +105,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'])] + #[Groups(['bom_entry:read', 'bom_entry:write', 'import', 'simple', 'extended', 'full'])] 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'])] + #[Groups(['bom_entry:read', 'bom_entry:write', 'import', 'extended', 'full'])] protected string $comment = ''; /** @@ -120,7 +120,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'])] + #[Groups(['bom_entry:read', 'bom_entry:write', ])] protected ?Project $project = null; /** @@ -128,7 +128,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'])] + #[Groups(['bom_entry:read', 'bom_entry:write', 'full'])] protected ?Part $part = null; /** @@ -136,7 +136,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'])] + #[Groups(['bom_entry:read', 'bom_entry:write', 'import', 'extended', 'full'])] protected ?BigDecimal $price = null; /** diff --git a/src/Entity/UserSystem/ApiToken.php b/src/Entity/UserSystem/ApiToken.php index 751c08c1..f5cbf541 100644 --- a/src/Entity/UserSystem/ApiToken.php +++ b/src/Entity/UserSystem/ApiToken.php @@ -75,10 +75,10 @@ class ApiToken implements TimeStampableInterface #[Groups('token:read')] private ?User $user = null; - #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)] + #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)] #[Groups('token:read')] #[Year2038BugWorkaround] - private ?\DateTime $valid_until; + private ?\DateTimeImmutable $valid_until; #[ORM\Column(length: 68, unique: true)] private string $token; @@ -87,9 +87,9 @@ class ApiToken implements TimeStampableInterface #[Groups('token:read')] private ApiTokenLevel $level = ApiTokenLevel::READ_ONLY; - #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)] + #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)] #[Groups('token:read')] - private ?\DateTime $last_time_used = null; + private ?\DateTimeImmutable $last_time_used = null; public function __construct(ApiTokenType $tokenType = ApiTokenType::PERSONAL_ACCESS_TOKEN) { @@ -97,7 +97,7 @@ class ApiToken implements TimeStampableInterface $this->token = $tokenType->getTokenPrefix() . bin2hex(random_bytes(32)); //By default, tokens are valid for 1 year. - $this->valid_until = new \DateTime('+1 year'); + $this->valid_until = new \DateTimeImmutable('+1 year'); } public function getTokenType(): ApiTokenType @@ -116,7 +116,7 @@ class ApiToken implements TimeStampableInterface return $this; } - public function getValidUntil(): ?\DateTimeInterface + public function getValidUntil(): ?\DateTimeImmutable { return $this->valid_until; } @@ -127,10 +127,10 @@ class ApiToken implements TimeStampableInterface */ public function isValid(): bool { - return $this->valid_until === null || $this->valid_until > new \DateTime(); + return $this->valid_until === null || $this->valid_until > new \DateTimeImmutable(); } - public function setValidUntil(?\DateTime $valid_until): ApiToken + public function setValidUntil(?\DateTimeImmutable $valid_until): ApiToken { $this->valid_until = $valid_until; return $this; @@ -159,19 +159,17 @@ class ApiToken implements TimeStampableInterface /** * Gets the last time the token was used to authenticate or null if it was never used. - * @return \DateTimeInterface|null */ - public function getLastTimeUsed(): ?\DateTimeInterface + public function getLastTimeUsed(): ?\DateTimeImmutable { return $this->last_time_used; } /** * Sets the last time the token was used to authenticate. - * @param \DateTime|null $last_time_used * @return ApiToken */ - public function setLastTimeUsed(?\DateTime $last_time_used): ApiToken + public function setLastTimeUsed(?\DateTimeImmutable $last_time_used): ApiToken { $this->last_time_used = $last_time_used; return $this; diff --git a/src/Entity/UserSystem/Group.php b/src/Entity/UserSystem/Group.php index 32056cc9..6da9d35f 100644 --- a/src/Entity/UserSystem/Group.php +++ b/src/Entity/UserSystem/Group.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\UserSystem; +use Doctrine\Common\Collections\Criteria; use App\Entity\Attachments\Attachment; use App\Validator\Constraints\NoLockout; use Doctrine\DBAL\Types\Types; @@ -49,7 +50,7 @@ use Symfony\Component\Validator\Constraints as Assert; class Group extends AbstractStructuralDBElement implements HasPermissionsInterface { #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $children; #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] @@ -74,7 +75,7 @@ class Group extends AbstractStructuralDBElement implements HasPermissionsInterfa */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: GroupAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] protected Collection $attachments; #[ORM\ManyToOne(targetEntity: GroupAttachment::class)] @@ -91,7 +92,7 @@ class Group extends AbstractStructuralDBElement implements HasPermissionsInterfa */ #[Assert\Valid] #[ORM\OneToMany(mappedBy: 'element', targetEntity: GroupParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] + #[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])] protected Collection $parameters; public function __construct() diff --git a/src/Entity/UserSystem/User.php b/src/Entity/UserSystem/User.php index 7d8c181a..b5dd6064 100644 --- a/src/Entity/UserSystem/User.php +++ b/src/Entity/UserSystem/User.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\UserSystem; +use Doctrine\Common\Collections\Criteria; use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; @@ -101,7 +102,7 @@ use Jbtronics\TFAWebauthn\Model\TwoFactorInterface as WebauthnTwoFactorInterface #[ApiFilter(LikeFilter::class, properties: ["name", "aboutMe"])] #[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)] #[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] -#[NoLockout] +#[NoLockout(groups: ['permissions:edit'])] class User extends AttachmentContainingDBElement implements UserInterface, HasPermissionsInterface, TwoFactorInterface, BackupCodeInterface, TrustedDeviceInterface, WebauthnTwoFactorInterface, PreferredProviderInterface, PasswordAuthenticatedUserInterface, SamlUserInterface { @@ -116,10 +117,10 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe protected ?int $id = null; #[Groups(['user:read'])] - protected ?\DateTime $lastModified = null; + protected ?\DateTimeImmutable $lastModified = null; #[Groups(['user:read'])] - protected ?\DateTime $addedDate = null; + protected ?\DateTimeImmutable $addedDate = null; /** * @var bool Determines if the user is disabled (user can not log in) @@ -143,9 +144,11 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe protected ?string $pw_reset_token = null; #[ORM\Column(name: 'config_instock_comment_a', type: Types::TEXT)] + #[Groups(['extended', 'full', 'import'])] protected string $instock_comment_a = ''; #[ORM\Column(name: 'config_instock_comment_w', type: Types::TEXT)] + #[Groups(['extended', 'full', 'import'])] protected string $instock_comment_w = ''; /** @@ -267,7 +270,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe * @var Collection */ #[ORM\OneToMany(mappedBy: 'element', targetEntity: UserAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] - #[ORM\OrderBy(['name' => 'ASC'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] #[Groups(['user:read', 'user:write'])] protected Collection $attachments; @@ -276,11 +279,11 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe #[Groups(['user:read', 'user:write'])] protected ?Attachment $master_picture_attachment = null; - /** @var \DateTimeInterface|null The time when the backup codes were generated + /** @var \DateTimeImmutable|null The time when the backup codes were generated */ #[Groups(['full'])] - #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)] - protected ?\DateTimeInterface $backupCodesGenerationDate = null; + #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)] + protected ?\DateTimeImmutable $backupCodesGenerationDate = null; /** @var Collection */ @@ -317,10 +320,10 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe protected ?PermissionData $permissions = null; /** - * @var \DateTime|null the time until the password reset token is valid + * @var \DateTimeImmutable|null the time until the password reset token is valid */ - #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)] - protected ?\DateTime $pw_reset_expires = null; + #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)] + protected ?\DateTimeImmutable $pw_reset_expires = null; /** * @var bool True if the user was created by a SAML provider (and therefore cannot change its password) @@ -527,7 +530,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe /** * Gets the datetime when the password reset token expires. */ - public function getPwResetExpires(): \DateTimeInterface|null + public function getPwResetExpires(): \DateTimeImmutable|null { return $this->pw_reset_expires; } @@ -535,7 +538,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe /** * Sets the datetime when the password reset token expires. */ - public function setPwResetExpires(\DateTime $pw_reset_expires): self + public function setPwResetExpires(\DateTimeImmutable $pw_reset_expires): self { $this->pw_reset_expires = $pw_reset_expires; @@ -896,7 +899,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe public function setBackupCodes(array $codes): self { $this->backupCodes = $codes; - $this->backupCodesGenerationDate = $codes === [] ? null : new DateTime(); + $this->backupCodesGenerationDate = $codes === [] ? null : new \DateTimeImmutable(); return $this; } @@ -904,7 +907,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe /** * Return the date when the backup codes were generated. */ - public function getBackupCodesGenerationDate(): ?\DateTimeInterface + public function getBackupCodesGenerationDate(): ?\DateTimeImmutable { return $this->backupCodesGenerationDate; } diff --git a/src/Entity/UserSystem/WebauthnKey.php b/src/Entity/UserSystem/WebauthnKey.php index 68868fc6..b2716e07 100644 --- a/src/Entity/UserSystem/WebauthnKey.php +++ b/src/Entity/UserSystem/WebauthnKey.php @@ -82,9 +82,8 @@ class WebauthnKey extends BasePublicKeyCredentialSource implements TimeStampable /** * Retrieve the last time when the key was used. - * @return \DateTimeInterface|null */ - public function getLastTimeUsed(): ?\DateTimeInterface + public function getLastTimeUsed(): ?\DateTimeImmutable { return $this->last_time_used; } diff --git a/src/EventSubscriber/LogSystem/EventLoggerSubscriber.php b/src/EventListener/LogSystem/EventLoggerListener.php similarity index 97% rename from src/EventSubscriber/LogSystem/EventLoggerSubscriber.php rename to src/EventListener/LogSystem/EventLoggerListener.php index b3f07a9b..6fe3d8dc 100644 --- a/src/EventSubscriber/LogSystem/EventLoggerSubscriber.php +++ b/src/EventListener/LogSystem/EventLoggerListener.php @@ -20,7 +20,7 @@ declare(strict_types=1); -namespace App\EventSubscriber\LogSystem; +namespace App\EventListener\LogSystem; use App\Entity\Attachments\Attachment; use App\Entity\Base\AbstractDBElement; @@ -38,7 +38,7 @@ use App\Entity\UserSystem\User; use App\Services\LogSystem\EventCommentHelper; use App\Services\LogSystem\EventLogger; use App\Services\LogSystem\EventUndoHelper; -use Doctrine\Common\EventSubscriber; +use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Event\OnFlushEventArgs; use Doctrine\ORM\Event\PostFlushEventArgs; @@ -50,7 +50,10 @@ use Symfony\Component\Serializer\SerializerInterface; /** * This event subscriber writes to the event log when entities are changed, removed, created. */ -class EventLoggerSubscriber implements EventSubscriber +#[AsDoctrineListener(event: Events::onFlush)] +#[AsDoctrineListener(event: Events::postPersist)] +#[AsDoctrineListener(event: Events::postFlush)] +class EventLoggerListener { /** * @var array The given fields will not be saved, because they contain sensitive information @@ -187,15 +190,6 @@ class EventLoggerSubscriber implements EventSubscriber return true; } - public function getSubscribedEvents(): array - { - return[ - Events::onFlush, - Events::postPersist, - Events::postFlush, - ]; - } - protected function logElementDeleted(AbstractDBElement $entity, EntityManagerInterface $em): void { $log = new ElementDeletedLogEntry($entity); diff --git a/src/EventSubscriber/LogSystem/LogDBMigrationSubscriber.php b/src/EventListener/LogSystem/LogDBMigrationListener.php similarity index 92% rename from src/EventSubscriber/LogSystem/LogDBMigrationSubscriber.php rename to src/EventListener/LogSystem/LogDBMigrationListener.php index 07ec8ea4..c8b60e18 100644 --- a/src/EventSubscriber/LogSystem/LogDBMigrationSubscriber.php +++ b/src/EventListener/LogSystem/LogDBMigrationListener.php @@ -20,11 +20,11 @@ declare(strict_types=1); -namespace App\EventSubscriber\LogSystem; +namespace App\EventListener\LogSystem; use App\Entity\LogSystem\DatabaseUpdatedLogEntry; use App\Services\LogSystem\EventLogger; -use Doctrine\Common\EventSubscriber; +use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener; use Doctrine\Migrations\DependencyFactory; use Doctrine\Migrations\Event\MigrationsEventArgs; use Doctrine\Migrations\Events; @@ -32,7 +32,9 @@ use Doctrine\Migrations\Events; /** * This subscriber logs databaseMigrations to Event log. */ -class LogDBMigrationSubscriber implements EventSubscriber +#[AsDoctrineListener(event: Events::onMigrationsMigrated)] +#[AsDoctrineListener(event: Events::onMigrationsMigrating)] +class LogDBMigrationListener { protected ?string $old_version = null; protected ?string $new_version = null; diff --git a/src/Exceptions/TwigModeException.php b/src/Exceptions/TwigModeException.php index adcc86aa..b76d14d3 100644 --- a/src/Exceptions/TwigModeException.php +++ b/src/Exceptions/TwigModeException.php @@ -46,8 +46,23 @@ use Twig\Error\Error; class TwigModeException extends RuntimeException { + private const PROJECT_PATH = __DIR__ . '/../../'; + public function __construct(?Error $previous = null) { parent::__construct($previous->getMessage(), 0, $previous); } + + /** + * Returns the message of this exception, where it is tried to remove any sensitive information (like filepaths). + * @return string + */ + public function getSafeMessage(): string + { + //Resolve project root path + $projectPath = realpath(self::PROJECT_PATH); + + //Remove occurrences of the project path from the message + return str_replace($projectPath, '[Part-DB Root Folder]', $this->getMessage()); + } } diff --git a/src/Form/AttachmentFormType.php b/src/Form/AttachmentFormType.php index 109c6602..957d692b 100644 --- a/src/Form/AttachmentFormType.php +++ b/src/Form/AttachmentFormType.php @@ -142,7 +142,7 @@ class AttachmentFormType extends AbstractType if (!$file instanceof UploadedFile) { //When no file was uploaded, but a URL was entered, try to determine the attachment name from the URL - if (empty($attachment->getName()) && !empty($attachment->getURL())) { + if ((trim($attachment->getName()) === '') && ($attachment->getURL() !== null && $attachment->getURL() !== '')) { $name = basename(parse_url($attachment->getURL(), PHP_URL_PATH)); $attachment->setName($name); } diff --git a/src/Form/ParameterType.php b/src/Form/ParameterType.php index 517bb48c..4c2174ae 100644 --- a/src/Form/ParameterType.php +++ b/src/Form/ParameterType.php @@ -53,6 +53,7 @@ use App\Entity\Parameters\PartParameter; use App\Entity\Parameters\StorageLocationParameter; use App\Entity\Parameters\SupplierParameter; use App\Entity\Parts\MeasurementUnit; +use App\Form\Type\ExponentialNumberType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\NumberType; use Symfony\Component\Form\Extension\Core\Type\TextType; @@ -93,7 +94,7 @@ class ParameterType extends AbstractType ], ]); - $builder->add('value_max', NumberType::class, [ + $builder->add('value_max', ExponentialNumberType::class, [ 'label' => false, 'required' => false, 'html5' => true, @@ -101,10 +102,10 @@ class ParameterType extends AbstractType 'step' => 'any', 'placeholder' => 'parameters.max.placeholder', 'class' => 'form-control-sm', - 'style' => 'max-width: 12ch;', + 'style' => 'max-width: 25ch;', ], ]); - $builder->add('value_min', NumberType::class, [ + $builder->add('value_min', ExponentialNumberType::class, [ 'label' => false, 'required' => false, 'html5' => true, @@ -112,10 +113,10 @@ class ParameterType extends AbstractType 'step' => 'any', 'placeholder' => 'parameters.min.placeholder', 'class' => 'form-control-sm', - 'style' => 'max-width: 12ch;', + 'style' => 'max-width: 25ch;', ], ]); - $builder->add('value_typical', NumberType::class, [ + $builder->add('value_typical', ExponentialNumberType::class, [ 'label' => false, 'required' => false, 'html5' => true, @@ -123,7 +124,7 @@ class ParameterType extends AbstractType 'step' => 'any', 'placeholder' => 'parameters.typical.placeholder', 'class' => 'form-control-sm', - 'style' => 'max-width: 12ch;', + 'style' => 'max-width: 25ch;', ], ]); $builder->add('unit', TextType::class, [ diff --git a/src/Form/PasswordTypeExtension.php b/src/Form/PasswordTypeExtension.php index a911bf90..64711c53 100644 --- a/src/Form/PasswordTypeExtension.php +++ b/src/Form/PasswordTypeExtension.php @@ -1,4 +1,7 @@ . */ - namespace App\Form; use Symfony\Component\Form\AbstractTypeExtension; @@ -51,4 +53,4 @@ class PasswordTypeExtension extends AbstractTypeExtension $view->vars['password_estimator'] = $options['password_estimator']; } -} \ No newline at end of file +} diff --git a/src/Form/Permissions/PermissionsType.php b/src/Form/Permissions/PermissionsType.php index c0fbb4e8..86fdbc2c 100644 --- a/src/Form/Permissions/PermissionsType.php +++ b/src/Form/Permissions/PermissionsType.php @@ -45,9 +45,7 @@ class PermissionsType extends AbstractType $resolver->setDefaults([ 'show_legend' => true, 'show_presets' => false, - 'show_dependency_notice' => static function (Options $options) { - return !$options['disabled']; - }, + 'show_dependency_notice' => static fn(Options $options) => !$options['disabled'], 'constraints' => static function (Options $options) { if (!$options['disabled']) { return [new NoLockout()]; diff --git a/src/Form/ProjectSystem/ProjectAddPartsType.php b/src/Form/ProjectSystem/ProjectAddPartsType.php index ea51764a..61f72c41 100644 --- a/src/Form/ProjectSystem/ProjectAddPartsType.php +++ b/src/Form/ProjectSystem/ProjectAddPartsType.php @@ -1,4 +1,7 @@ . */ - namespace App\Form\ProjectSystem; use App\Entity\ProjectSystem\Project; @@ -83,4 +85,4 @@ class ProjectAddPartsType extends AbstractType $resolver->setAllowedTypes('project', ['null', Project::class]); } -} \ No newline at end of file +} diff --git a/src/Form/TFAGoogleSettingsType.php b/src/Form/TFAGoogleSettingsType.php index e00ba494..7917f705 100644 --- a/src/Form/TFAGoogleSettingsType.php +++ b/src/Form/TFAGoogleSettingsType.php @@ -53,6 +53,7 @@ class TFAGoogleSettingsType extends AbstractType 'google_confirmation', TextType::class, [ + 'label' => 'tfa.check.code.confirmation', 'mapped' => false, 'attr' => [ 'maxlength' => '6', @@ -60,7 +61,7 @@ class TFAGoogleSettingsType extends AbstractType 'pattern' => '\d*', 'autocomplete' => 'off', ], - 'constraints' => [new ValidGoogleAuthCode()], + 'constraints' => [new ValidGoogleAuthCode(groups: ["google_authenticator"])], ] ); @@ -92,6 +93,7 @@ class TFAGoogleSettingsType extends AbstractType { $resolver->setDefaults([ 'data_class' => User::class, + 'validation_groups' => ['google_authenticator'], ]); } } diff --git a/src/Form/Type/ExponentialNumberType.php b/src/Form/Type/ExponentialNumberType.php new file mode 100644 index 00000000..b76196d9 --- /dev/null +++ b/src/Form/Type/ExponentialNumberType.php @@ -0,0 +1,61 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Form\Type; + +use App\Form\Type\Helper\ExponentialNumberTransformer; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\NumberType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * Similar to the NumberType, but formats small values in scienfitic notation instead of rounding it to 0, like NumberType + */ +class ExponentialNumberType extends AbstractType +{ + public function getParent(): string + { + return NumberType::class; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + //We want to allow the full precision of the number, so disable rounding + 'scale' => null, + ]); + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder->resetViewTransformers(); + + $builder->addViewTransformer(new ExponentialNumberTransformer( + $options['scale'], + $options['grouping'], + $options['rounding_mode'], + $options['html5'] ? 'en' : null + )); + } +} \ No newline at end of file diff --git a/src/Form/Type/Helper/ExponentialNumberTransformer.php b/src/Form/Type/Helper/ExponentialNumberTransformer.php new file mode 100644 index 00000000..ee2f4a4c --- /dev/null +++ b/src/Form/Type/Helper/ExponentialNumberTransformer.php @@ -0,0 +1,113 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Form\Type\Helper; + +use Symfony\Component\Form\Exception\TransformationFailedException; +use Symfony\Component\Form\Extension\Core\DataTransformer\NumberToLocalizedStringTransformer; + +/** + * This transformer formats small values in scienfitic notation instead of rounding it to 0, like the default + * NumberFormatter. + */ +class ExponentialNumberTransformer extends NumberToLocalizedStringTransformer +{ + public function __construct( + private ?int $scale = null, + ?bool $grouping = false, + ?int $roundingMode = \NumberFormatter::ROUND_HALFUP, + protected ?string $locale = null + ) { + //Set scale to null, to disable rounding of values + parent::__construct($scale, $grouping, $roundingMode, $locale); + } + + /** + * Transforms a number type into localized number. + * + * @param int|float|null $value Number value + * + * @throws TransformationFailedException if the given value is not numeric + * or if the value cannot be transformed + */ + public function transform(mixed $value): string + { + if (null === $value) { + return ''; + } + + if (!is_numeric($value)) { + throw new TransformationFailedException('Expected a numeric.'); + } + + //If the value is too small, the number formatter would return 0, therfore use exponential notation for small numbers + if (abs($value) < 1e-3) { + $formatter = $this->getScientificNumberFormatter(); + } else { + $formatter = $this->getNumberFormatter(); + } + + + + $value = $formatter->format($value); + + if (intl_is_failure($formatter->getErrorCode())) { + throw new TransformationFailedException($formatter->getErrorMessage()); + } + + // Convert non-breaking and narrow non-breaking spaces to normal ones + $value = str_replace(["\xc2\xa0", "\xe2\x80\xaf"], ' ', $value); + + return $value; + } + + protected function getScientificNumberFormatter(): \NumberFormatter + { + $formatter = new \NumberFormatter($this->locale ?? \Locale::getDefault(), \NumberFormatter::SCIENTIFIC); + + if (null !== $this->scale) { + $formatter->setAttribute(\NumberFormatter::MAX_FRACTION_DIGITS, $this->scale); + $formatter->setAttribute(\NumberFormatter::ROUNDING_MODE, $this->roundingMode); + } + + $formatter->setAttribute(\NumberFormatter::GROUPING_USED, (int) $this->grouping); + + return $formatter; + } + + protected function getNumberFormatter(): \NumberFormatter + { + $formatter = parent::getNumberFormatter(); + + //Unset the fraction digits, as we don't want to round the number + $formatter->setAttribute(\NumberFormatter::FRACTION_DIGITS, 0); + if (null !== $this->scale) { + $formatter->setAttribute(\NumberFormatter::MAX_FRACTION_DIGITS, $this->scale); + } else { + $formatter->setAttribute(\NumberFormatter::MAX_FRACTION_DIGITS, 100); + } + + + return $formatter; + } +} \ No newline at end of file diff --git a/src/Form/Type/Helper/StructuralEntityChoiceHelper.php b/src/Form/Type/Helper/StructuralEntityChoiceHelper.php index 0c596f83..e8e19ad6 100644 --- a/src/Form/Type/Helper/StructuralEntityChoiceHelper.php +++ b/src/Form/Type/Helper/StructuralEntityChoiceHelper.php @@ -100,7 +100,7 @@ class StructuralEntityChoiceHelper public function generateChoiceAttrCurrency(Currency $choice, Options|array $options): array { $tmp = $this->generateChoiceAttr($choice, $options); - $symbol = empty($choice->getIsoCode()) ? null : Currencies::getSymbol($choice->getIsoCode()); + $symbol = $choice->getIsoCode() === '' ? null : Currencies::getSymbol($choice->getIsoCode()); $tmp['data-short'] = $options['short'] ? $symbol : $choice->getName(); //Show entities that are not added to DB yet separately from other entities diff --git a/src/Form/Type/Helper/StructuralEntityChoiceLoader.php b/src/Form/Type/Helper/StructuralEntityChoiceLoader.php index a527a44f..b79fad1b 100644 --- a/src/Form/Type/Helper/StructuralEntityChoiceLoader.php +++ b/src/Form/Type/Helper/StructuralEntityChoiceLoader.php @@ -52,11 +52,7 @@ class StructuralEntityChoiceLoader extends AbstractChoiceLoader protected function loadChoices(): iterable { //If the starting_element is set and not persisted yet, add it to the list - if ($this->starting_element !== null && $this->starting_element->getID() === null) { - $tmp = [$this->starting_element]; - } else { - $tmp = []; - } + $tmp = $this->starting_element !== null && $this->starting_element->getID() === null ? [$this->starting_element] : []; if ($this->additional_element) { $tmp = $this->createNewEntitiesFromValue($this->additional_element); @@ -163,7 +159,7 @@ class StructuralEntityChoiceLoader extends AbstractChoiceLoader // the same as the value that is generated for the same entity after it is persisted. // Otherwise, errors occurs that the element could not be found. foreach ($values as &$data) { - $data = trim($data); + $data = trim((string) $data); $data = preg_replace('/\s*->\s*/', '->', $data); } unset ($data); diff --git a/src/Form/Type/StructuralEntityType.php b/src/Form/Type/StructuralEntityType.php index 41ad1c97..1018eeeb 100644 --- a/src/Form/Type/StructuralEntityType.php +++ b/src/Form/Type/StructuralEntityType.php @@ -108,12 +108,8 @@ class StructuralEntityType extends AbstractType $resolver->setDefault('dto_value', null); $resolver->setAllowedTypes('dto_value', ['null', 'string']); //If no help text is explicitly set, we use the dto value as help text and show it as html - $resolver->setDefault('help', function (Options $options) { - return $this->dtoText($options['dto_value']); - }); - $resolver->setDefault('help_html', function (Options $options) { - return $options['dto_value'] !== null; - }); + $resolver->setDefault('help', fn(Options $options) => $this->dtoText($options['dto_value'])); + $resolver->setDefault('help_html', fn(Options $options) => $options['dto_value'] !== null); $resolver->setDefault('attr', function (Options $options) { $tmp = [ diff --git a/src/Form/UserAdminForm.php b/src/Form/UserAdminForm.php index d1e5924e..864bcf6b 100644 --- a/src/Form/UserAdminForm.php +++ b/src/Form/UserAdminForm.php @@ -57,6 +57,8 @@ class UserAdminForm extends AbstractType parent::configureOptions($resolver); // TODO: Change the autogenerated stub $resolver->setRequired('attachment_class'); $resolver->setDefault('parameter_class', false); + + $resolver->setDefault('validation_groups', ['Default', 'permissions:edit']); } public function buildForm(FormBuilderInterface $builder, array $options): void diff --git a/src/Helpers/FilenameSanatizer.php b/src/Helpers/FilenameSanatizer.php index 3dca5995..f6744b1a 100644 --- a/src/Helpers/FilenameSanatizer.php +++ b/src/Helpers/FilenameSanatizer.php @@ -36,6 +36,9 @@ class FilenameSanatizer */ public static function sanitizeFilename(string $filename): string { + //Convert to ASCII + $filename = iconv('UTF-8', 'ASCII//TRANSLIT', $filename); + $filename = preg_replace( '~ [<>:"/\\\|?*]| # file system reserved https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words @@ -47,9 +50,9 @@ class FilenameSanatizer '-', $filename); // avoids ".", ".." or ".hiddenFiles" - $filename = ltrim($filename, '.-'); + $filename = ltrim((string) $filename, '.-'); //Limit filename length to 255 bytes $ext = pathinfo($filename, PATHINFO_EXTENSION); - return mb_strcut(pathinfo($filename, PATHINFO_FILENAME), 0, 255 - ($ext ? strlen($ext) + 1 : 0), mb_detect_encoding($filename)) . ($ext ? '.' . $ext : ''); + return mb_strcut(pathinfo($filename, PATHINFO_FILENAME), 0, 255 - ($ext !== '' && $ext !== '0' ? strlen($ext) + 1 : 0), mb_detect_encoding($filename)) . ($ext !== '' && $ext !== '0' ? '.' . $ext : ''); } } \ No newline at end of file diff --git a/src/Helpers/LabelResponse.php b/src/Helpers/LabelResponse.php index 98451e2f..2973eb7e 100644 --- a/src/Helpers/LabelResponse.php +++ b/src/Helpers/LabelResponse.php @@ -84,7 +84,7 @@ class LabelResponse extends Response */ public function setAutoLastModified(): LabelResponse { - $this->setLastModified(new DateTime()); + $this->setLastModified(new \DateTimeImmutable()); return $this; } diff --git a/src/Helpers/Projects/ProjectBuildRequest.php b/src/Helpers/Projects/ProjectBuildRequest.php index c2c2ad90..430d37b5 100644 --- a/src/Helpers/Projects/ProjectBuildRequest.php +++ b/src/Helpers/Projects/ProjectBuildRequest.php @@ -174,11 +174,7 @@ final class ProjectBuildRequest */ public function getLotWithdrawAmount(PartLot|int $lot): float { - if ($lot instanceof PartLot) { - $lot_id = $lot->getID(); - } else { // Then it must be an int - $lot_id = $lot; - } + $lot_id = $lot instanceof PartLot ? $lot->getID() : $lot; if (! array_key_exists($lot_id, $this->withdraw_amounts)) { throw new \InvalidArgumentException('The given lot is not in the withdraw amounts array!'); diff --git a/src/Helpers/TrinaryLogicHelper.php b/src/Helpers/TrinaryLogicHelper.php index 54ab9bf8..f4b460de 100644 --- a/src/Helpers/TrinaryLogicHelper.php +++ b/src/Helpers/TrinaryLogicHelper.php @@ -26,6 +26,7 @@ namespace App\Helpers; /** * Helper functions for logic operations with trinary logic. * True and false are represented as classical boolean values, undefined is represented as null. + * @see \App\Tests\Helpers\TrinaryLogicHelperTest */ class TrinaryLogicHelper { diff --git a/src/Migration/WithPermPresetsTrait.php b/src/Migration/WithPermPresetsTrait.php index debbade8..5182f3b6 100644 --- a/src/Migration/WithPermPresetsTrait.php +++ b/src/Migration/WithPermPresetsTrait.php @@ -64,7 +64,7 @@ trait WithPermPresetsTrait public function setContainer(ContainerInterface $container = null): void { - if ($container) { + if ($container !== null) { $this->container = $container; $this->permission_presets_helper = $container->get(PermissionPresetsHelper::class); } diff --git a/src/Repository/AttachmentContainingDBElementRepository.php b/src/Repository/AttachmentContainingDBElementRepository.php index 7c81c38a..40869662 100644 --- a/src/Repository/AttachmentContainingDBElementRepository.php +++ b/src/Repository/AttachmentContainingDBElementRepository.php @@ -30,6 +30,7 @@ use Doctrine\ORM\Mapping\ClassMetadata; /** * @template TEntityClass of AttachmentContainingDBElement * @extends NamedDBElementRepository + * @see \App\Tests\Repository\AttachmentContainingDBElementRepositoryTest */ class AttachmentContainingDBElementRepository extends NamedDBElementRepository { diff --git a/src/Repository/DBElementRepository.php b/src/Repository/DBElementRepository.php index 90bd5927..2437e848 100644 --- a/src/Repository/DBElementRepository.php +++ b/src/Repository/DBElementRepository.php @@ -49,6 +49,7 @@ use ReflectionClass; /** * @template TEntityClass of AbstractDBElement * @extends EntityRepository + * @see \App\Tests\Repository\DBElementRepositoryTest */ class DBElementRepository extends EntityRepository { @@ -143,9 +144,7 @@ class DBElementRepository extends EntityRepository */ protected function sortResultArrayByIDArray(array &$result_array, array $ids): void { - usort($result_array, static function (AbstractDBElement $a, AbstractDBElement $b) use ($ids) { - return array_search($a->getID(), $ids, true) <=> array_search($b->getID(), $ids, true); - }); + usort($result_array, static fn(AbstractDBElement $a, AbstractDBElement $b) => array_search($a->getID(), $ids, true) <=> array_search($b->getID(), $ids, true)); } protected function setField(AbstractDBElement $element, string $field, int $new_value): void diff --git a/src/Repository/LogEntryRepository.php b/src/Repository/LogEntryRepository.php index 8e16cdf8..bf9909c5 100644 --- a/src/Repository/LogEntryRepository.php +++ b/src/Repository/LogEntryRepository.php @@ -111,13 +111,13 @@ class LogEntryRepository extends DBElementRepository { $qb = $this->createQueryBuilder('log'); $qb->select('log') - //->where('log INSTANCE OF App\Entity\LogSystem\ElementEditedLogEntry') ->where('log INSTANCE OF '.ElementEditedLogEntry::class) ->orWhere('log INSTANCE OF '.CollectionElementDeleted::class) ->andWhere('log.target_type = :target_type') ->andWhere('log.target_id = :target_id') ->andWhere('log.timestamp >= :until') - ->orderBy('log.timestamp', 'DESC'); + ->orderBy('log.timestamp', 'DESC') + ; $qb->setParameter('target_type', LogTargetType::fromElementClass($element)); $qb->setParameter('target_id', $element->getID()); @@ -142,7 +142,7 @@ class LogEntryRepository extends DBElementRepository ->andWhere('log.target_type = :target_type') ->andWhere('log.target_id = :target_id') ->andWhere('log.timestamp >= :until') - ->orderBy('log.timestamp', 'DESC'); + ; $qb->setParameter('target_type', LogTargetType::fromElementClass($element)); $qb->setParameter('target_id', $element->getID()); @@ -225,7 +225,10 @@ class LogEntryRepository extends DBElementRepository ->leftJoin('log.user', 'user') ->andWhere('log.target_type = :target_type') ->andWhere('log.target_id = :target_id') - ->orderBy('log.timestamp', 'DESC'); + ->orderBy('log.timestamp', 'DESC') + //Use id as fallback, if timestamp is the same (higher id means newer entry) + ->addOrderBy('log.id', 'DESC') + ; $qb->setParameter('target_type', LogTargetType::fromElementClass($element)); $qb->setParameter('target_id', $element->getID()); diff --git a/src/Repository/NamedDBElementRepository.php b/src/Repository/NamedDBElementRepository.php index e62c64e7..8c78cb5e 100644 --- a/src/Repository/NamedDBElementRepository.php +++ b/src/Repository/NamedDBElementRepository.php @@ -29,6 +29,7 @@ use App\Helpers\Trees\TreeViewNode; /** * @template TEntityClass of AbstractNamedDBElement * @extends DBElementRepository + * @see \App\Tests\Repository\NamedDBElementRepositoryTest */ class NamedDBElementRepository extends DBElementRepository { diff --git a/src/Repository/StructuralDBElementRepository.php b/src/Repository/StructuralDBElementRepository.php index c7548cc9..47c85db3 100644 --- a/src/Repository/StructuralDBElementRepository.php +++ b/src/Repository/StructuralDBElementRepository.php @@ -52,7 +52,7 @@ class StructuralDBElementRepository extends AttachmentContainingDBElementReposit $qb->select('e') ->orderBy('NATSORT(e.name)', $nameOrdering); - if ($parent) { + if ($parent !== null) { $qb->where('e.parent = :parent') ->setParameter('parent', $parent); } else { @@ -260,7 +260,7 @@ class StructuralDBElementRepository extends AttachmentContainingDBElementReposit //Try to find if we already have an element cached for this name $entity = $this->getNewEntityFromCache($name, null); - if ($entity) { + if ($entity !== null) { return $entity; } diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php index 9f24520d..bbaa2b39 100644 --- a/src/Repository/UserRepository.php +++ b/src/Repository/UserRepository.php @@ -36,6 +36,7 @@ use Symfony\Component\Security\Core\User\UserInterface; * @method User[] findAll() * @method User[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) * @extends NamedDBElementRepository + * @see \App\Tests\Repository\UserRepositoryTest */ final class UserRepository extends NamedDBElementRepository implements PasswordUpgraderInterface { diff --git a/src/Security/ApiTokenAuthenticator.php b/src/Security/ApiTokenAuthenticator.php index 5d121556..23ab68b9 100644 --- a/src/Security/ApiTokenAuthenticator.php +++ b/src/Security/ApiTokenAuthenticator.php @@ -75,7 +75,7 @@ class ApiTokenAuthenticator implements AuthenticatorInterface $old_time = $token->getLastTimeUsed(); //Set the last used date of the token - $token->setLastTimeUsed(new \DateTime()); + $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 diff --git a/src/Security/TwoFactor/WebauthnKeyLastUseTwoFactorProvider.php b/src/Security/TwoFactor/WebauthnKeyLastUseTwoFactorProvider.php index 5d67e36f..9bfa691d 100644 --- a/src/Security/TwoFactor/WebauthnKeyLastUseTwoFactorProvider.php +++ b/src/Security/TwoFactor/WebauthnKeyLastUseTwoFactorProvider.php @@ -44,12 +44,12 @@ class WebauthnKeyLastUseTwoFactorProvider implements TwoFactorProviderInterface public function __construct( #[AutowireDecorated] - private TwoFactorProviderInterface $decorated, - private EntityManagerInterface $entityManager, + private readonly TwoFactorProviderInterface $decorated, + private readonly EntityManagerInterface $entityManager, #[Autowire(service: 'jbtronics_webauthn_tfa.user_public_key_source_repo')] - private UserPublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository, + private readonly UserPublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository, #[Autowire(service: 'jbtronics_webauthn_tfa.webauthn_provider')] - private WebauthnProvider $webauthnProvider, + private readonly WebauthnProvider $webauthnProvider, ) { } diff --git a/src/Serializer/APIPlatform/DetermineTypeFromElementIRIDenormalizer.php b/src/Serializer/APIPlatform/DetermineTypeFromElementIRIDenormalizer.php index 863fbee6..8283dbbe 100644 --- a/src/Serializer/APIPlatform/DetermineTypeFromElementIRIDenormalizer.php +++ b/src/Serializer/APIPlatform/DetermineTypeFromElementIRIDenormalizer.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace App\Serializer\APIPlatform; +use ApiPlatform\Metadata\Exception\ResourceClassNotFoundException; use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Post; @@ -59,7 +60,7 @@ class DetermineTypeFromElementIRIDenormalizer implements DenormalizerInterface, * @param array $input * @param Operation $operation * @return array - * @throws \ApiPlatform\Metadata\Exception\ResourceClassNotFoundException + * @throws ResourceClassNotFoundException */ private function addTypeDiscriminatorIfNecessary(array $input, Operation $operation): array { diff --git a/src/Serializer/PartNormalizer.php b/src/Serializer/PartNormalizer.php index a4890104..650b0214 100644 --- a/src/Serializer/PartNormalizer.php +++ b/src/Serializer/PartNormalizer.php @@ -161,7 +161,7 @@ class PartNormalizer implements NormalizerInterface, DenormalizerInterface, Norm if (isset($data['supplier']) && $data['supplier'] !== "") { $supplier = $this->locationDenormalizer->denormalize($data['supplier'], Supplier::class, $format, $context); - if ($supplier) { + if ($supplier !== null) { $orderdetail = new Orderdetail(); $orderdetail->setSupplier($supplier); diff --git a/src/Serializer/StructuralElementDenormalizer.php b/src/Serializer/StructuralElementDenormalizer.php index 93bcf0b2..17b3d81e 100644 --- a/src/Serializer/StructuralElementDenormalizer.php +++ b/src/Serializer/StructuralElementDenormalizer.php @@ -24,23 +24,28 @@ namespace App\Serializer; use App\Entity\Base\AbstractStructuralDBElement; use App\Repository\StructuralDBElementRepository; +use App\Serializer\APIPlatform\SkippableItemNormalizer; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; +use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; /** * @see \App\Tests\Serializer\StructuralElementDenormalizerTest */ -class StructuralElementDenormalizer implements DenormalizerInterface +class StructuralElementDenormalizer implements DenormalizerInterface, DenormalizerAwareInterface { + use DenormalizerAwareTrait; + + private const ALREADY_CALLED = 'STRUCTURAL_DENORMALIZER_ALREADY_CALLED'; + private array $object_cache = []; public function __construct( - private readonly EntityManagerInterface $entityManager, - #[Autowire(service: ObjectNormalizer::class)] - private readonly DenormalizerInterface $denormalizer) + private readonly EntityManagerInterface $entityManager) { } @@ -51,6 +56,13 @@ class StructuralElementDenormalizer implements DenormalizerInterface return false; } + //If we already handled this object, skip it + if (isset($context[self::ALREADY_CALLED]) + && is_array($context[self::ALREADY_CALLED]) + && in_array($data, $context[self::ALREADY_CALLED], true)) { + return false; + } + return is_array($data) && is_subclass_of($type, AbstractStructuralDBElement::class) //Only denormalize if we are doing a file import operation @@ -59,6 +71,16 @@ class StructuralElementDenormalizer implements DenormalizerInterface public function denormalize($data, string $type, string $format = null, array $context = []): ?AbstractStructuralDBElement { + //Do not use API Platform's denormalizer + $context[SkippableItemNormalizer::DISABLE_ITEM_NORMALIZER] = true; + + if (!isset($context[self::ALREADY_CALLED])) { + $context[self::ALREADY_CALLED] = []; + } + + $context[self::ALREADY_CALLED][] = $data; + + /** @var AbstractStructuralDBElement $deserialized_entity */ $deserialized_entity = $this->denormalizer->denormalize($data, $type, $format, $context); diff --git a/src/Serializer/StructuralElementNormalizer.php b/src/Serializer/StructuralElementNormalizer.php index 105aad76..1838f210 100644 --- a/src/Serializer/StructuralElementNormalizer.php +++ b/src/Serializer/StructuralElementNormalizer.php @@ -48,10 +48,7 @@ class StructuralElementNormalizer implements NormalizerInterface return $data instanceof AbstractStructuralDBElement; } - /** - * @return array - */ - public function normalize($object, string $format = null, array $context = []): array + public function normalize($object, string $format = null, array $context = []): mixed { if (!$object instanceof AbstractStructuralDBElement) { throw new \InvalidArgumentException('This normalizer only supports AbstractStructural objects!'); @@ -59,6 +56,11 @@ class StructuralElementNormalizer implements NormalizerInterface $data = $this->normalizer->normalize($object, $format, $context); + //If the data is not an array, we can't do anything with it + if (!is_array($data)) { + return $data; + } + //Remove type field for CSV export if ($format === 'csv') { unset($data['type']); diff --git a/src/Services/Attachments/AttachmentPathResolver.php b/src/Services/Attachments/AttachmentPathResolver.php index e6015b3a..e3e7a3ca 100644 --- a/src/Services/Attachments/AttachmentPathResolver.php +++ b/src/Services/Attachments/AttachmentPathResolver.php @@ -139,12 +139,12 @@ class AttachmentPathResolver } //If we have now have a placeholder left, the string is invalid: - if (preg_match('#%\w+%#', $placeholder_path)) { + if (preg_match('#%\w+%#', (string) $placeholder_path)) { return null; } //Path is invalid if path is directory traversal - if (str_contains($placeholder_path, '..')) { + if (str_contains((string) $placeholder_path, '..')) { return null; } @@ -183,7 +183,7 @@ class AttachmentPathResolver } //If the new string does not begin with a placeholder, it is invalid - if (!preg_match('#^%\w+%#', $real_path)) { + if (!preg_match('#^%\w+%#', (string) $real_path)) { return null; } diff --git a/src/Services/Attachments/AttachmentSubmitHandler.php b/src/Services/Attachments/AttachmentSubmitHandler.php index d5fe9370..d9b2a380 100644 --- a/src/Services/Attachments/AttachmentSubmitHandler.php +++ b/src/Services/Attachments/AttachmentSubmitHandler.php @@ -300,7 +300,7 @@ class AttachmentSubmitHandler return $attachment; } - $filename = basename($old_path); + $filename = basename((string) $old_path); //If the basename is not one of the new unique on, we have to save the old filename if (!preg_match('#\w+-\w{13}\.#', $filename)) { //Save filename to attachment field @@ -378,7 +378,7 @@ class AttachmentSubmitHandler //If we don't know filename yet, try to determine it out of url if ('' === $filename) { - $filename = basename(parse_url($url, PHP_URL_PATH)); + $filename = basename(parse_url((string) $url, PHP_URL_PATH)); } //Set original file diff --git a/src/Services/Cache/ElementCacheTagGenerator.php b/src/Services/Cache/ElementCacheTagGenerator.php index 382c9317..88fca09f 100644 --- a/src/Services/Cache/ElementCacheTagGenerator.php +++ b/src/Services/Cache/ElementCacheTagGenerator.php @@ -46,11 +46,10 @@ class ElementCacheTagGenerator { //Ensure that the given element is a class name if (is_object($element)) { - $element = get_class($element); - } else { //And that the class exists - if (!class_exists($element)) { - throw new \InvalidArgumentException("The given class '$element' does not exist!"); - } + $element = $element::class; + } elseif (!class_exists($element)) { + //And that the class exists + throw new \InvalidArgumentException("The given class '$element' does not exist!"); } //Check if the tag is already cached diff --git a/src/Services/Misc/DBInfoHelper.php b/src/Services/Doctrine/DBInfoHelper.php similarity index 84% rename from src/Services/Misc/DBInfoHelper.php rename to src/Services/Doctrine/DBInfoHelper.php index 620da702..160e2d89 100644 --- a/src/Services/Misc/DBInfoHelper.php +++ b/src/Services/Doctrine/DBInfoHelper.php @@ -1,4 +1,22 @@ . + */ declare(strict_types=1); @@ -20,10 +38,10 @@ declare(strict_types=1); * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -namespace App\Services\Misc; +namespace App\Services\Doctrine; -use Doctrine\DBAL\Exception; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Platforms\SQLitePlatform; diff --git a/src/Services/Doctrine/NatsortDebugHelper.php b/src/Services/Doctrine/NatsortDebugHelper.php new file mode 100644 index 00000000..fe5b77aa --- /dev/null +++ b/src/Services/Doctrine/NatsortDebugHelper.php @@ -0,0 +1,86 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Services\Doctrine; + +use App\Doctrine\Functions\Natsort; +use App\Entity\Parts\Part; +use Doctrine\ORM\EntityManagerInterface; + +/** + * This service allows to debug the natsort function by showing various information about the current state of + * the natsort function. + */ +class NatsortDebugHelper +{ + public function __construct(private readonly EntityManagerInterface $entityManager) + { + // This is a dummy constructor + } + + /** + * Check if the slow natural sort is allowed on the Natsort function. + * If it is not, then the request handler might need to be adjusted. + * @return bool + */ + public function isSlowNaturalSortAllowed(): bool + { + return Natsort::isSlowNaturalSortAllowed(); + } + + public function getNaturalSortMethod(): string + { + //Construct a dummy query which uses the Natsort function + $query = $this->entityManager->createQuery('SELECT natsort(1) FROM ' . Part::class . ' p'); + $sql = $query->getSQL(); + //Remove the leading SELECT and the trailing semicolon + $sql = substr($sql, 7, -1); + + //Remove AS and everything afterwards + $sql = preg_replace('/\s+AS\s+.*/', '', $sql); + + //If just 1 is returned, then we use normal (non-natural sorting) + if ($sql === '1') { + return 'Disabled'; + } + + if (str_contains( $sql, 'COLLATE numeric')) { + return 'Native (PostgreSQL)'; + } + + if (str_contains($sql, 'NATURAL_SORT_KEY')) { + return 'Native (MariaDB)'; + } + + if (str_contains($sql, 'COLLATE NATURAL_CMP')) { + return 'Emulation via PHP (SQLite)'; + } + + if (str_contains($sql, 'NatSortKey')) { + return 'Emulation via custom function (MySQL)'; + } + + + return 'Unknown ('. $sql . ')'; + } +} \ No newline at end of file diff --git a/src/Services/EDA/KiCadHelper.php b/src/Services/EDA/KiCadHelper.php index 05630d16..13de01e8 100644 --- a/src/Services/EDA/KiCadHelper.php +++ b/src/Services/EDA/KiCadHelper.php @@ -134,7 +134,7 @@ class KiCadHelper if ($this->category_depth >= 0) { //Ensure that the category is set - if (!$category) { + if ($category === null) { throw new NotFoundHttpException('Category must be set, if category_depth is greater than 1!'); } @@ -196,25 +196,25 @@ class KiCadHelper //Add basic fields $result["fields"]["description"] = $this->createField($part->getDescription()); - if ($part->getCategory()) { + if ($part->getCategory() !== null) { $result["fields"]["Category"] = $this->createField($part->getCategory()->getFullPath('/')); } - if ($part->getManufacturer()) { + if ($part->getManufacturer() !== null) { $result["fields"]["Manufacturer"] = $this->createField($part->getManufacturer()->getName()); } if ($part->getManufacturerProductNumber() !== "") { $result['fields']["MPN"] = $this->createField($part->getManufacturerProductNumber()); } - if ($part->getManufacturingStatus()) { + if ($part->getManufacturingStatus() !== null) { $result["fields"]["Manufacturing Status"] = $this->createField( //Always use the english translation $this->translator->trans($part->getManufacturingStatus()->toTranslationKey(), locale: 'en') ); } - if ($part->getFootprint()) { + if ($part->getFootprint() !== null) { $result["fields"]["Part-DB Footprint"] = $this->createField($part->getFootprint()->getName()); } - if ($part->getPartUnit()) { + if ($part->getPartUnit() !== null) { $unit = $part->getPartUnit()->getName(); if ($part->getPartUnit()->getUnit() !== "") { $unit .= ' ('.$part->getPartUnit()->getUnit().')'; @@ -225,7 +225,7 @@ class KiCadHelper $result["fields"]["Mass"] = $this->createField($part->getMass() . ' g'); } $result["fields"]["Part-DB ID"] = $this->createField($part->getId()); - if (!empty($part->getIpn())) { + if ($part->getIpn() !== null && $part->getIpn() !== '' && $part->getIpn() !== '0') { $result["fields"]["Part-DB IPN"] = $this->createField($part->getIpn()); } @@ -308,14 +308,9 @@ class KiCadHelper )) { return true; } - //And on the footprint - if ($part->getFootprint() && $part->getFootprint()->getEdaInfo()->getKicadFootprint() !== null) { - return true; - } - //Otherwise the part should be not visible - return false; + return $part->getFootprint() && $part->getFootprint()->getEdaInfo()->getKicadFootprint() !== null; } /** diff --git a/src/Services/EntityMergers/EntityMerger.php b/src/Services/EntityMergers/EntityMerger.php index 0f1355ea..c0be84ee 100644 --- a/src/Services/EntityMergers/EntityMerger.php +++ b/src/Services/EntityMergers/EntityMerger.php @@ -69,7 +69,7 @@ class EntityMerger { $merger = $this->findMergerForObject($target, $other, $context); if ($merger === null) { - throw new \RuntimeException('No merger found for merging '.get_class($other).' into '.get_class($target)); + throw new \RuntimeException('No merger found for merging '.$other::class.' into '.$target::class); } return $merger->merge($target, $other, $context); } diff --git a/src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php b/src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php index dcd0984a..a5c9a5fa 100644 --- a/src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php +++ b/src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php @@ -85,9 +85,7 @@ trait EntityMergerHelperTrait protected function useOtherValueIfNotNull(object $target, object $other, string $field): object { return $this->useCallback( - function ($target_value, $other_value) { - return $target_value ?? $other_value; - }, + fn($target_value, $other_value) => $target_value ?? $other_value, $target, $other, $field @@ -106,9 +104,7 @@ trait EntityMergerHelperTrait protected function useOtherValueIfNotEmtpy(object $target, object $other, string $field): object { return $this->useCallback( - function ($target_value, $other_value) { - return empty($target_value) ? $other_value : $target_value; - }, + fn($target_value, $other_value) => empty($target_value) ? $other_value : $target_value, $target, $other, $field @@ -126,9 +122,7 @@ trait EntityMergerHelperTrait protected function useLargerValue(object $target, object $other, string $field): object { return $this->useCallback( - function ($target_value, $other_value) { - return max($target_value, $other_value); - }, + fn($target_value, $other_value) => max($target_value, $other_value), $target, $other, $field @@ -146,9 +140,7 @@ trait EntityMergerHelperTrait protected function useSmallerValue(object $target, object $other, string $field): object { return $this->useCallback( - function ($target_value, $other_value) { - return min($target_value, $other_value); - }, + fn($target_value, $other_value) => min($target_value, $other_value), $target, $other, $field @@ -166,9 +158,7 @@ trait EntityMergerHelperTrait protected function useTrueValue(object $target, object $other, string $field): object { return $this->useCallback( - function (bool $target_value, bool $other_value): bool { - return $target_value || $other_value; - }, + fn(bool $target_value, bool $other_value): bool => $target_value || $other_value, $target, $other, $field @@ -232,10 +222,8 @@ trait EntityMergerHelperTrait continue 2; } } - } else { - if ($target_collection->contains($item)) { - continue; - } + } elseif ($target_collection->contains($item)) { + continue; } $clones[] = clone $item; @@ -257,11 +245,9 @@ trait EntityMergerHelperTrait */ protected function mergeAttachments(AttachmentContainingDBElement $target, AttachmentContainingDBElement $other): object { - return $this->mergeCollections($target, $other, 'attachments', function (Attachment $t, Attachment $o): bool { - return $t->getName() === $o->getName() - && $t->getAttachmentType() === $o->getAttachmentType() - && $t->getPath() === $o->getPath(); - }); + return $this->mergeCollections($target, $other, 'attachments', fn(Attachment $t, Attachment $o): bool => $t->getName() === $o->getName() + && $t->getAttachmentType() === $o->getAttachmentType() + && $t->getPath() === $o->getPath()); } /** @@ -272,16 +258,14 @@ trait EntityMergerHelperTrait */ protected function mergeParameters(AbstractStructuralDBElement|Part $target, AbstractStructuralDBElement|Part $other): object { - return $this->mergeCollections($target, $other, 'parameters', function (AbstractParameter $t, AbstractParameter $o): bool { - return $t->getName() === $o->getName() - && $t->getSymbol() === $o->getSymbol() - && $t->getUnit() === $o->getUnit() - && $t->getValueMax() === $o->getValueMax() - && $t->getValueMin() === $o->getValueMin() - && $t->getValueTypical() === $o->getValueTypical() - && $t->getValueText() === $o->getValueText() - && $t->getGroup() === $o->getGroup(); - }); + return $this->mergeCollections($target, $other, 'parameters', fn(AbstractParameter $t, AbstractParameter $o): bool => $t->getName() === $o->getName() + && $t->getSymbol() === $o->getSymbol() + && $t->getUnit() === $o->getUnit() + && $t->getValueMax() === $o->getValueMax() + && $t->getValueMin() === $o->getValueMin() + && $t->getValueTypical() === $o->getValueTypical() + && $t->getValueText() === $o->getValueText() + && $t->getGroup() === $o->getGroup()); } /** diff --git a/src/Services/EntityMergers/Mergers/PartMerger.php b/src/Services/EntityMergers/Mergers/PartMerger.php index 29d4fe5c..4ce779e8 100644 --- a/src/Services/EntityMergers/Mergers/PartMerger.php +++ b/src/Services/EntityMergers/Mergers/PartMerger.php @@ -33,6 +33,7 @@ use App\Entity\PriceInformations\Orderdetail; * This class merges two parts together. * * @implements EntityMergerInterface + * @see \App\Tests\Services\EntityMergers\Mergers\PartMergerTest */ class PartMerger implements EntityMergerInterface { @@ -99,7 +100,7 @@ class PartMerger implements EntityMergerInterface return $target; } - private static function comparePartAssociations(PartAssociation $t, PartAssociation $o): bool { + private function comparePartAssociations(PartAssociation $t, PartAssociation $o): bool { //We compare the translation keys, as it contains info about the type and other type info return $t->getOther() === $o->getOther() && $t->getTypeTranslationKey() === $o->getTypeTranslationKey(); @@ -117,7 +118,7 @@ class PartMerger implements EntityMergerInterface $this->mergeParameters($target, $other); //Merge the associations - $this->mergeCollections($target, $other, 'associated_parts_as_owner', self::comparePartAssociations(...)); + $this->mergeCollections($target, $other, 'associated_parts_as_owner', $this->comparePartAssociations(...)); //We have to recreate the associations towards the other part, as they are not created by the merger foreach ($other->getAssociatedPartsAsOther() as $association) { @@ -131,7 +132,7 @@ class PartMerger implements EntityMergerInterface } //Ensure that the association is not already present foreach ($owner->getAssociatedPartsAsOwner() as $existing_association) { - if (self::comparePartAssociations($existing_association, $clone)) { + if ($this->comparePartAssociations($existing_association, $clone)) { continue 2; } } diff --git a/src/Services/EntityURLGenerator.php b/src/Services/EntityURLGenerator.php index 16aadb0e..5718daec 100644 --- a/src/Services/EntityURLGenerator.php +++ b/src/Services/EntityURLGenerator.php @@ -360,11 +360,7 @@ class EntityURLGenerator */ protected function mapToController(array $map, string|AbstractDBElement $entity): string { - if (is_string($entity)) { //If a class name was already passed, then use it directly - $class = $entity; - } else { //Otherwise get the class name from the entity - $class = $entity::class; - } + $class = is_string($entity) ? $entity : $entity::class; //Check if we have an direct mapping for the given class if (!array_key_exists($class, $map)) { diff --git a/src/Services/ImportExportSystem/EntityExporter.php b/src/Services/ImportExportSystem/EntityExporter.php index 2619c975..c37db50c 100644 --- a/src/Services/ImportExportSystem/EntityExporter.php +++ b/src/Services/ImportExportSystem/EntityExporter.php @@ -23,9 +23,13 @@ declare(strict_types=1); namespace App\Services\ImportExportSystem; use App\Entity\Base\AbstractNamedDBElement; +use App\Entity\Base\AbstractStructuralDBElement; use App\Helpers\FilenameSanatizer; +use App\Serializer\APIPlatform\SkippableItemNormalizer; use Symfony\Component\OptionsResolver\OptionsResolver; use InvalidArgumentException; +use Symfony\Component\Serializer\Exception\CircularReferenceException; +use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use function is_array; use ReflectionClass; use ReflectionException; @@ -97,10 +101,27 @@ class EntityExporter 'csv_delimiter' => $options['csv_delimiter'], 'xml_root_node_name' => 'PartDBExport', 'partdb_export' => true, + //Skip the item normalizer, so that we dont get IRIs in the output + SkippableItemNormalizer::DISABLE_ITEM_NORMALIZER => true, + //Handle circular references + AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => $this->handleCircularReference(...), ] ); } + private function handleCircularReference(object $object, string $format, array $context): string + { + if ($object instanceof AbstractStructuralDBElement) { + return $object->getFullPath("->"); + } elseif ($object instanceof AbstractNamedDBElement) { + return $object->getName(); + } elseif ($object instanceof \Stringable) { + return $object->__toString(); + } + + throw new CircularReferenceException('Circular reference detected for object of type '.get_class($object)); + } + /** * Exports an Entity or an array of entities to multiple file formats. * diff --git a/src/Services/ImportExportSystem/EntityImporter.php b/src/Services/ImportExportSystem/EntityImporter.php index 5f73ae92..1318c658 100644 --- a/src/Services/ImportExportSystem/EntityImporter.php +++ b/src/Services/ImportExportSystem/EntityImporter.php @@ -29,6 +29,7 @@ use App\Entity\Parts\Part; use App\Repository\StructuralDBElementRepository; use App\Serializer\APIPlatform\SkippableItemNormalizer; use Symfony\Component\Validator\ConstraintViolationList; +use Symfony\Component\Validator\ConstraintViolationListInterface; use function count; use Doctrine\ORM\EntityManagerInterface; use InvalidArgumentException; @@ -43,6 +44,12 @@ use Symfony\Component\Validator\Validator\ValidatorInterface; */ class EntityImporter { + + /** + * The encodings that are supported by the importer, and that should be autodeceted. + */ + private const ENCODINGS = ["ASCII", "UTF-8", "ISO-8859-1", "ISO-8859-15", "Windows-1252", "UTF-16", "UTF-32"]; + public function __construct(protected SerializerInterface $serializer, protected EntityManagerInterface $em, protected ValidatorInterface $validator) { } @@ -57,12 +64,16 @@ class EntityImporter * @phpstan-param class-string $class_name * @param AbstractStructuralDBElement|null $parent the element which will be used as parent element for new elements * @param array $errors an associative array containing all validation errors + * @param-out array $errors * * @return AbstractNamedDBElement[] An array containing all valid imported entities (with the type $class_name) * @return T[] */ public function massCreation(string $lines, string $class_name, ?AbstractStructuralDBElement $parent = null, array &$errors = []): array { + //Try to detect the text encoding of the data and convert it to UTF-8 + $lines = mb_convert_encoding($lines, 'UTF-8', mb_detect_encoding($lines, self::ENCODINGS)); + //Expand every line to a single entry: $names = explode("\n", $lines); @@ -152,10 +163,14 @@ class EntityImporter * @param string $data The serialized data which should be imported * @param array $options The options for the import process * @param array $errors An array which will be filled with the validation errors, if any occurs during import + * @param-out array $errors * @return array An array containing all valid imported entities */ public function importString(string $data, array $options = [], array &$errors = []): array { + //Try to detect the text encoding of the data and convert it to UTF-8 + $data = mb_convert_encoding($data, 'UTF-8', mb_detect_encoding($data, self::ENCODINGS)); + $resolver = new OptionsResolver(); $this->configureOptions($resolver); $options = $resolver->resolve($options); @@ -218,6 +233,10 @@ class EntityImporter if (count($tmp) > 0) { //Log validation errors to global log. $name = $entity instanceof AbstractStructuralDBElement ? $entity->getFullPath() : $entity->getName(); + if (trim($name) === '') { + $name = 'Row ' . (string) $key; + } + $errors[$name] = [ 'violations' => $tmp, 'entity' => $entity, @@ -264,7 +283,7 @@ class EntityImporter * @param array $options options for the import process * @param AbstractNamedDBElement[] $entities The imported entities are returned in this array * - * @return array An associative array containing an ConstraintViolationList and the entity name as key are returned, + * @return array An associative array containing an ConstraintViolationList and the entity name as key are returned, * if an error happened during validation. When everything was successfully, the array should be empty. */ public function importFileAndPersistToDB(File $file, array $options = [], array &$entities = []): array @@ -296,6 +315,7 @@ class EntityImporter * * @param File $file the file that should be used for importing * @param array $options options for the import process + * @param-out array $errors * * @return array an array containing the deserialized elements */ diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php index ea897b9f..574bc32b 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php @@ -225,7 +225,7 @@ trait PKImportHelperTrait protected function setCreationDate(TimeStampableInterface $entity, ?string $datetime_str): void { if ($datetime_str !== null && $datetime_str !== '' && $datetime_str !== '0000-00-00 00:00:00') { - $date = new \DateTime($datetime_str); + $date = new \DateTimeImmutable($datetime_str); } else { $date = null; //Null means "now" at persist time } diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKOptionalImporter.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKOptionalImporter.php index b8e8272e..fafde29a 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/PKOptionalImporter.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKOptionalImporter.php @@ -116,7 +116,7 @@ class PKOptionalImporter //All imported users get assigned to the "PartKeepr Users" group $group_users = $this->em->find(Group::class, 3); $group = $this->em->getRepository(Group::class)->findOneBy(['name' => 'PartKeepr Users', 'parent' => $group_users]); - if (!$group) { + if ($group === null) { $group = new Group(); $group->setName('PartKeepr Users'); $group->setParent($group_users); diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php index 767f36b4..9dd67233 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php @@ -218,7 +218,7 @@ class PKPartImporter 'iso_code' => $currency_iso_code, ]); - if (!$currency) { + if ($currency === null) { $currency = new Currency(); $currency->setIsoCode($currency_iso_code); $currency->setName(Currencies::getName($currency_iso_code)); @@ -265,7 +265,7 @@ class PKPartImporter ]); //When no orderdetail exists, create one - if (!$orderdetail) { + if ($orderdetail === null) { $orderdetail = new Orderdetail(); $orderdetail->setSupplier($supplier); $orderdetail->setSupplierpartnr($spn); diff --git a/src/Services/InfoProviderSystem/DTOs/FileDTO.php b/src/Services/InfoProviderSystem/DTOs/FileDTO.php index 295cf78a..0d1db76a 100644 --- a/src/Services/InfoProviderSystem/DTOs/FileDTO.php +++ b/src/Services/InfoProviderSystem/DTOs/FileDTO.php @@ -26,6 +26,7 @@ namespace App\Services\InfoProviderSystem\DTOs; /** * This DTO represents a file that can be downloaded from a URL. * This could be a datasheet, a 3D model, a picture or similar. + * @see \App\Tests\Services\InfoProviderSystem\DTOs\FileDTOTest */ class FileDTO { diff --git a/src/Services/InfoProviderSystem/DTOs/ParameterDTO.php b/src/Services/InfoProviderSystem/DTOs/ParameterDTO.php index d9a0596c..3332700b 100644 --- a/src/Services/InfoProviderSystem/DTOs/ParameterDTO.php +++ b/src/Services/InfoProviderSystem/DTOs/ParameterDTO.php @@ -26,6 +26,7 @@ namespace App\Services\InfoProviderSystem\DTOs; /** * This DTO represents a parameter of a part (similar to the AbstractParameter entity). * This could be a voltage, a current, a temperature or similar. + * @see \App\Tests\Services\InfoProviderSystem\DTOs\ParameterDTOTest */ class ParameterDTO { @@ -76,7 +77,7 @@ class ParameterDTO $parts = preg_split('/\s*(\.{3}|~)\s*/', $value); if (count($parts) === 2) { //Try to extract number and unit from value (allow leading +) - if (empty($unit)) { + if ($unit === null || trim($unit) === '') { [$number, $unit] = self::splitIntoValueAndUnit(ltrim($parts[0], " +")) ?? [$parts[0], null]; } else { $number = $parts[0]; diff --git a/src/Services/InfoProviderSystem/DTOs/PurchaseInfoDTO.php b/src/Services/InfoProviderSystem/DTOs/PurchaseInfoDTO.php index 6073cc5f..bcd8be43 100644 --- a/src/Services/InfoProviderSystem/DTOs/PurchaseInfoDTO.php +++ b/src/Services/InfoProviderSystem/DTOs/PurchaseInfoDTO.php @@ -25,6 +25,7 @@ namespace App\Services\InfoProviderSystem\DTOs; /** * This DTO represents a purchase information for a part (supplier name, order number and prices). + * @see \App\Tests\Services\InfoProviderSystem\DTOs\PurchaseInfoDTOTest */ class PurchaseInfoDTO { diff --git a/src/Services/InfoProviderSystem/DTOs/SearchResultDTO.php b/src/Services/InfoProviderSystem/DTOs/SearchResultDTO.php index c12b54f8..28943702 100644 --- a/src/Services/InfoProviderSystem/DTOs/SearchResultDTO.php +++ b/src/Services/InfoProviderSystem/DTOs/SearchResultDTO.php @@ -27,6 +27,7 @@ use App\Entity\Parts\ManufacturingStatus; /** * This DTO represents a search result for a part. + * @see \App\Tests\Services\InfoProviderSystem\DTOs\SearchResultDTOTest */ class SearchResultDTO { diff --git a/src/Services/InfoProviderSystem/DTOtoEntityConverter.php b/src/Services/InfoProviderSystem/DTOtoEntityConverter.php index a0d1baf0..2c2b4076 100644 --- a/src/Services/InfoProviderSystem/DTOtoEntityConverter.php +++ b/src/Services/InfoProviderSystem/DTOtoEntityConverter.php @@ -45,6 +45,7 @@ use Doctrine\ORM\EntityManagerInterface; /** * This class converts DTOs to entities which can be persisted in the DB + * @see \App\Tests\Services\InfoProviderSystem\DTOtoEntityConverterTest */ final class DTOtoEntityConverter { @@ -127,7 +128,7 @@ final class DTOtoEntityConverter $entity->setAttachmentType($type); //If no name is given, try to extract the name from the URL - if (empty($dto->name)) { + if ($dto->name === null || $dto->name === '' || $dto->name === '0') { $entity->setName($this->getAttachmentNameFromURL($dto->url)); } else { $entity->setName($dto->name); diff --git a/src/Services/InfoProviderSystem/ProviderRegistry.php b/src/Services/InfoProviderSystem/ProviderRegistry.php index acfbdc1e..f6c398d2 100644 --- a/src/Services/InfoProviderSystem/ProviderRegistry.php +++ b/src/Services/InfoProviderSystem/ProviderRegistry.php @@ -27,6 +27,7 @@ use App\Services\InfoProviderSystem\Providers\InfoProviderInterface; /** * This class keeps track of all registered info providers and allows to find them by their key + * @see \App\Tests\Services\InfoProviderSystem\ProviderRegistryTest */ final class ProviderRegistry { diff --git a/src/Services/InfoProviderSystem/Providers/DigikeyProvider.php b/src/Services/InfoProviderSystem/Providers/DigikeyProvider.php index 1ffbd836..d8e93321 100644 --- a/src/Services/InfoProviderSystem/Providers/DigikeyProvider.php +++ b/src/Services/InfoProviderSystem/Providers/DigikeyProvider.php @@ -45,6 +45,14 @@ class DigikeyProvider implements InfoProviderInterface private readonly HttpClientInterface $digikeyClient; + /** + * A list of parameter IDs, that are always assumed as text only and will never be converted to a numerical value. + * This allows to fix issues like #682, where the "Supplier Device Package" was parsed as a numerical value. + */ + private const TEXT_ONLY_PARAMETERS = [ + 1291, //Supplier Device Package + 39246, //Package / Case + ]; public function __construct(HttpClientInterface $httpClient, private readonly OAuthTokenManager $authTokenManager, private readonly string $currency, private readonly string $clientId, @@ -93,7 +101,7 @@ class DigikeyProvider implements InfoProviderInterface public function isActive(): bool { //The client ID has to be set and a token has to be available (user clicked connect) - return !empty($this->clientId) && $this->authTokenManager->hasToken(self::OAUTH_APP_NAME); + return $this->clientId !== '' && $this->authTokenManager->hasToken(self::OAUTH_APP_NAME); } public function searchByKeyword(string $keyword): array @@ -210,11 +218,16 @@ class DigikeyProvider implements InfoProviderInterface $footprint_name = $parameter['Value']; } - if (in_array(trim($parameter['Value']), array('', '-'), true)) { + if (in_array(trim((string) $parameter['Value']), ['', '-'], true)) { continue; } - $results[] = ParameterDTO::parseValueIncludingUnit($parameter['Parameter'], $parameter['Value']); + //If the parameter was marked as text only, then we do not try to parse it as a numerical value + if (in_array($parameter['ParameterId'], self::TEXT_ONLY_PARAMETERS, true)) { + $results[] = new ParameterDTO(name: $parameter['Parameter'], value_text: $parameter['Value']); + } else { //Otherwise try to parse it as a numerical value + $results[] = ParameterDTO::parseValueIncludingUnit($parameter['Parameter'], $parameter['Value']); + } } return $results; diff --git a/src/Services/InfoProviderSystem/Providers/Element14Provider.php b/src/Services/InfoProviderSystem/Providers/Element14Provider.php index 085c9e50..ad70f9d9 100644 --- a/src/Services/InfoProviderSystem/Providers/Element14Provider.php +++ b/src/Services/InfoProviderSystem/Providers/Element14Provider.php @@ -65,7 +65,7 @@ class Element14Provider implements InfoProviderInterface public function isActive(): bool { - return !empty($this->api_key); + return $this->api_key !== ''; } /** diff --git a/src/Services/InfoProviderSystem/Providers/LCSCProvider.php b/src/Services/InfoProviderSystem/Providers/LCSCProvider.php index b065bed4..601d5ebc 100755 --- a/src/Services/InfoProviderSystem/Providers/LCSCProvider.php +++ b/src/Services/InfoProviderSystem/Providers/LCSCProvider.php @@ -96,17 +96,17 @@ class LCSCProvider implements InfoProviderInterface */ private function getRealDatasheetUrl(?string $url): string { - if (!empty($url) && preg_match("/^https:\/\/(datasheet\.lcsc\.com|www\.lcsc\.com\/datasheet)\/.*(C\d+)\.pdf$/", $url, $matches) > 0) { + if ($url !== null && trim($url) !== '' && preg_match("/^https:\/\/(datasheet\.lcsc\.com|www\.lcsc\.com\/datasheet)\/.*(C\d+)\.pdf$/", $url, $matches) > 0) { $response = $this->lcscClient->request('GET', $url, [ 'headers' => [ 'Referer' => 'https://www.lcsc.com/product-detail/_' . $matches[2] . '.html' ], ]); - if (preg_match('/(pdfUrl): ?("[^"]+wmsc\.lcsc\.com[^"]+\.pdf")/', $response->getContent(), $matches) > 0) { + if (preg_match('/(previewPdfUrl): ?("[^"]+wmsc\.lcsc\.com[^"]+\.pdf")/', $response->getContent(), $matches) > 0) { //HACKY: The URL string contains escaped characters like \u002F, etc. To decode it, the JSON decoding is reused //See https://github.com/Part-DB/Part-DB-server/pull/582#issuecomment-2033125934 $jsonObj = json_decode('{"' . $matches[1] . '": ' . $matches[2] . '}'); - $url = $jsonObj->pdfUrl; + $url = $jsonObj->previewPdfUrl; } } return $url; @@ -139,7 +139,7 @@ class LCSCProvider implements InfoProviderInterface // LCSC does not display LCSC codes in the search, instead taking you directly to the // detailed product listing. It does so utilizing a product tip field. // If product tip exists and there are no products in the product list try a detail query - if (count($products) === 0 && !($tipProductCode === null)) { + if (count($products) === 0 && $tipProductCode !== null) { $result[] = $this->queryDetail($tipProductCode); } @@ -174,11 +174,11 @@ class LCSCProvider implements InfoProviderInterface { // Get product images in advance $product_images = $this->getProductImages($product['productImages'] ?? null); - $product['productImageUrl'] = $product['productImageUrl'] ?? null; + $product['productImageUrl'] ??= null; // If the product does not have a product image but otherwise has attached images, use the first one. if (count($product_images) > 0) { - $product['productImageUrl'] = $product['productImageUrl'] ?? $product_images[0]->url; + $product['productImageUrl'] ??= $product_images[0]->url; } // LCSC puts HTML in footprints and descriptions sometimes randomly @@ -321,7 +321,7 @@ class LCSCProvider implements InfoProviderInterface foreach ($attributes as $attribute) { //Skip this attribute if it's empty - if (in_array(trim($attribute['paramValueEn']), array('', '-'), true)) { + if (in_array(trim((string) $attribute['paramValueEn']), ['', '-'], true)) { continue; } diff --git a/src/Services/InfoProviderSystem/Providers/MouserProvider.php b/src/Services/InfoProviderSystem/Providers/MouserProvider.php index 1e58285c..84f5d65b 100644 --- a/src/Services/InfoProviderSystem/Providers/MouserProvider.php +++ b/src/Services/InfoProviderSystem/Providers/MouserProvider.php @@ -74,7 +74,7 @@ class MouserProvider implements InfoProviderInterface public function isActive(): bool { - return !empty($this->api_key); + return $this->api_key !== ''; } public function searchByKeyword(string $keyword): array @@ -177,7 +177,7 @@ class MouserProvider implements InfoProviderInterface } if (count($tmp) > 1) { - throw new \RuntimeException('Multiple parts found with ID '.$id); + throw new \RuntimeException('Multiple parts found with ID '.$id . ' ('.count($tmp).' found). This is basically a bug in Mousers API response. See issue #616.'); } return $tmp[0]; @@ -211,6 +211,12 @@ class MouserProvider implements InfoProviderInterface $result = []; foreach ($products as $product) { + //Check if we have a valid product number. We assume that a product number, must have at least 4 characters + //Otherwise filter it out + if (strlen($product['MouserPartNumber']) < 4) { + continue; + } + //Check if we have a mass field available $mass = null; if (isset($product['UnitWeightKg']['UnitWeight'])) { @@ -247,7 +253,7 @@ class MouserProvider implements InfoProviderInterface private function parseDataSheets(?string $sheetUrl, ?string $sheetName): ?array { - if (empty($sheetUrl)) { + if ($sheetUrl === null || $sheetUrl === '' || $sheetUrl === '0') { return null; } $result = []; diff --git a/src/Services/InfoProviderSystem/Providers/OEMSecretsProvider.php b/src/Services/InfoProviderSystem/Providers/OEMSecretsProvider.php new file mode 100644 index 00000000..f3b59adc --- /dev/null +++ b/src/Services/InfoProviderSystem/Providers/OEMSecretsProvider.php @@ -0,0 +1,1476 @@ +. + */ + +/** + * OEMSecretsProvider Class + * + * This class is responsible for interfacing with the OEMSecrets API (version 3.0.1) to retrieve and manage information + * about electronic components. Since the API does not provide a unique identifier for each part, the class aggregates + * results based on "part_number" and "manufacturer_id". It also transforms unstructured descriptions into structured + * parameters and aggregates datasheets and images provided by multiple distributors. + * The OEMSecrets API returns results by matching the provided part number not only with the original part number + * but also with the distributor-assigned part number and/or the part description. + * + * Key functionalities: + * - Aggregation of results based on part_number and manufacturer_id to ensure unique identification of parts. + * - Conversion of component descriptions into structured parameters (ParameterDTO) for better clarity and searchability. + * - Aggregation of datasheets and images from multiple distributors, ensuring that all available resources are collected. + * - Price handling, including filtering of distributors that offer zero prices, controlled by the `zero_price` configuration variable. + * - A sorting algorithm that first prioritizes exact matches with the keyword, followed by alphabetical sorting of items + * with the same prefix (e.g., "BC546", "BC546A", "BC546B"), and finally, sorts by either manufacturer or completeness + * based on the specified criteria. + * - Sorting the distributors: + * 1. Environment's country_code first. + * 2. Region matching environment's country_code, prioritizing "Global" ('XX'). + * 3. Distributors with null country_code/region are placed last. + * 4. Final fallback is alphabetical sorting by region and country_code. + * + * Configuration: + * - The ZERO_PRICE variable must be set in the `.env.local` file. If is set to 0, the class will skip distributors + * that do not offer valid prices for the components. + * - Currency and country settings can also be specified for localized pricing and distributor filtering. + * - Generation of parameters: if SET_PARAM is set to 1 the parameters for the part are generated from the description + * transforming unstructured descriptions into structured parameters; each parameter in description should have the form: + * "...;name1:value1;name2:value2" + * - Sorting is guided by SORT_CRITERIA variable. The sorting process first arranges items based on the provided keyword. + * Then, if set to 'C', it further sorts by completeness (prioritizing items with the most detailed information). + * If set to 'M', it further sorts by manufacturer name. If unset or set to any other value, no sorting is performed. + * Distributors within each item are further sorted based on country_code and region, following the rules explained + * in the previous comment. + * + * Data Handling: + * - The class divides and stores component information across multiple session arrays: + * - `basic_info_results`: Stores basic information like name, description, manufacturer, and category. + * - `datasheets_results`: Aggregates datasheets provided by distributors, ensuring no duplicates. + * - `images_results`: Collects images of components from various sources, preventing duplication. + * - `parameters_results`: Extracts and stores key parameters parsed from component descriptions. + * - `purchase_info_results`: Contains detailed purchasing information like pricing and distributor details. + * + * - By splitting the data into separate session arrays, the class optimizes memory usage and simplifies retrieval + * of specific details without loading the entire dataset at once. + * + * Technical Details: + * - Uses OEMSecrets API (version 3.0.1) to retrieve component data. + * - Data processing includes sanitizing input, avoiding duplicates, and dynamically adjusting information as new distributor + * data becomes available (e.g., adding missing datasheets or parameters from subsequent API responses). + * + * @package App\Services\InfoProviderSystem\Providers + * @author Pasquale D'Orsi (https://github.com/pdo59) + * @version 1.2.0 + * @since 2024 August + */ + + +declare(strict_types=1); + +namespace App\Services\InfoProviderSystem\Providers; + +use App\Entity\Parts\ManufacturingStatus; +use App\Services\InfoProviderSystem\DTOs\FileDTO; +use App\Services\InfoProviderSystem\DTOs\PartDetailDTO; +use App\Services\InfoProviderSystem\DTOs\PriceDTO; +use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO; +use App\Services\InfoProviderSystem\DTOs\ParameterDTO; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Psr\Cache\CacheItemPoolInterface; + + +class OEMSecretsProvider implements InfoProviderInterface +{ + + private const ENDPOINT_URL = 'https://oemsecretsapi.com/partsearch'; + + public function __construct( + private readonly HttpClientInterface $oemsecretsClient, + private readonly string $api_key, + private readonly string $country_code, + private readonly string $currency, + private readonly string $zero_price, + private readonly string $set_param, + private readonly string $sort_criteria, + private readonly CacheItemPoolInterface $partInfoCache + ) + { + } + + private array $countryNameToCodeMap = [ + 'Andorra' => 'AD', + 'United Arab Emirates' => 'AE', + 'Antarctica' => 'AQ', + 'Argentina' => 'AR', + 'Austria' => 'AT', + 'Australia' => 'AU', + 'Belgium' => 'BE', + 'Bolivia' => 'BO', + 'Brazil' => 'BR', + 'Bouvet Island' => 'BV', + 'Belarus' => 'BY', + 'Canada' => 'CA', + 'Switzerland' => 'CH', + 'Chile' => 'CL', + 'China' => 'CN', + 'Colombia' => 'CO', + 'Czech Republic' => 'CZ', + 'Germany' => 'DE', + 'Denmark' => 'DK', + 'Ecuador' => 'EC', + 'Estonia' => 'EE', + 'Western Sahara' => 'EH', + 'Spain' => 'ES', + 'Finland' => 'FI', + 'Falkland Islands' => 'FK', + 'Faroe Islands' => 'FO', + 'France' => 'FR', + 'United Kingdom' => 'GB', + 'Georgia' => 'GE', + 'French Guiana' => 'GF', + 'Guernsey' => 'GG', + 'Gibraltar' => 'GI', + 'Greenland' => 'GL', + 'Greece' => 'GR', + 'South Georgia and the South Sandwich Islands' => 'GS', + 'Guyana' => 'GY', + 'Hong Kong' => 'HK', + 'Heard Island and McDonald Islands' => 'HM', + 'Croatia' => 'HR', + 'Hungary' => 'HU', + 'Ireland' => 'IE', + 'Isle of Man' => 'IM', + 'India' => 'IN', + 'Iceland' => 'IS', + 'Italy' => 'IT', + 'Jamaica' => 'JM', + 'Japan' => 'JP', + 'North Korea' => 'KP', + 'South Korea' => 'KR', + 'Kazakhstan' => 'KZ', + 'Liechtenstein' => 'LI', + 'Sri Lanka' => 'LK', + 'Lithuania' => 'LT', + 'Luxembourg' => 'LU', + 'Monaco' => 'MC', + 'Moldova' => 'MD', + 'Montenegro' => 'ME', + 'North Macedonia' => 'MK', + 'Malta' => 'MT', + 'Netherlands' => 'NL', + 'Norway' => 'NO', + 'New Zealand' => 'NZ', + 'Peru' => 'PE', + 'Philippines' => 'PH', + 'Poland' => 'PL', + 'Portugal' => 'PT', + 'Paraguay' => 'PY', + 'Romania' => 'RO', + 'Serbia' => 'RS', + 'Russia' => 'RU', + 'Solomon Islands' => 'SB', + 'Sudan' => 'SD', + 'Sweden' => 'SE', + 'Singapore' => 'SG', + 'Slovenia' => 'SI', + 'Svalbard and Jan Mayen' => 'SJ', + 'Slovakia' => 'SK', + 'San Marino' => 'SM', + 'Somalia' => 'SO', + 'Suriname' => 'SR', + 'Syria' => 'SY', + 'Eswatini' => 'SZ', + 'Turks and Caicos Islands' => 'TC', + 'French Southern Territories' => 'TF', + 'Togo' => 'TG', + 'Thailand' => 'TH', + 'Tajikistan' => 'TJ', + 'Tokelau' => 'TK', + 'Turkmenistan' => 'TM', + 'Tunisia' => 'TN', + 'Tonga' => 'TO', + 'Turkey' => 'TR', + 'Trinidad and Tobago' => 'TT', + 'Tuvalu' => 'TV', + 'Taiwan' => 'TW', + 'Tanzania' => 'TZ', + 'Ukraine' => 'UA', + 'Uganda' => 'UG', + 'United States Minor Outlying Islands' => 'UM', + 'United States' => 'US', + 'Uruguay' => 'UY', + 'Uzbekistan' => 'UZ', + 'Vatican City' => 'VA', + 'Venezuela' => 'VE', + 'British Virgin Islands' => 'VG', + 'U.S. Virgin Islands' => 'VI', + 'Vietnam' => 'VN', + 'Vanuatu' => 'VU', + 'Wallis and Futuna' => 'WF', + 'Yemen' => 'YE', + 'South Africa' => 'ZA', + 'Zambia' => 'ZM', + 'Zimbabwe' => 'ZW', + 'Global' => 'XX' + ]; + + private array $distributorCountryCodes = []; + private array $countryCodeToRegionMap = []; + + /** + * Get information about this provider + * + * @return array An associative array with the following keys (? means optional): + * - name: The (user friendly) name of the provider (e.g. "Digikey"), will be translated + * - description?: A short description of the provider (e.g. "Digikey is a ..."), will be translated + * - logo?: The logo of the provider (e.g. "digikey.png") + * - url?: The url of the provider (e.g. "https://www.digikey.com") + * - disabled_help?: A help text which is shown when the provider is disabled, explaining how to enable it + * - oauth_app_name?: The name of the OAuth app which is used for authentication (e.g. "ip_digikey_oauth"). If this is set a connect button will be shown + * + * @phpstan-return array{ name: string, description?: string, logo?: string, url?: string, disabled_help?: string, oauth_app_name?: string } + */ + public function getProviderInfo(): array + { + return [ + 'name' => 'OEMSecrets', + 'description' => 'This provider uses the OEMSecrets API to search for parts.', + 'url' => 'https://www.oemsecrets.com/', + 'disabled_help' => 'Configure the API key in the PROVIDER_OEMSECRETS_KEY environment variable to enable.' + ]; + } + /** + * Returns a unique key for this provider, which will be saved into the database + * and used to identify the provider + * @return string A unique key for this provider (e.g. "digikey") + */ + public function getProviderKey(): string + { + return 'oemsecrets'; + } + + /** + * Checks if this provider is enabled or not (meaning that it can be used for searching) + * @return bool True if the provider is enabled, false otherwise + */ + public function isActive(): bool + { + return $this->api_key !== ''; + } + + + /** + * Searches for products based on a given keyword using the OEMsecrets Part Search API. + * + * This method queries the OEMsecrets API to retrieve distributor data for the provided part number, + * including details such as pricing, compliance, and inventory. It supports both direct API queries + * and debugging with local JSON files. The results are processed, cached, and then sorted based + * on the keyword and specified criteria. + * + * @param string $keyword The part number to search for + * @return array An array of processed product details, sorted by relevance and additional criteria. + * + * @throws \Exception If the JSON file used for debugging is not found or contains errors. + */ + public function searchByKeyword(string $keyword): array + { + /* + oemsecrets Part Search API 3.0.1 + + "https://oemsecretsapi.com/partsearch? + searchTerm=BC547 + &apiKey=icawpb0bspoo2c6s64uv4vpdfp2vgr7e27bxw0yct2bzh87mpl027x353uelpq2x + ¤cy=EUR + &countryCode=IT" + + partsearch description: + Use the Part Search API to find distributor data for a full or partial manufacturer + part number including part details, pricing, compliance and inventory. + + Required Parameter Format Description + searchTerm string Part number you are searching for + apiKey string Your unique API key provided to you by OEMsecrets + + Additional Parameter Format Description + countryCode string The country you want to output for + currency string / array The currency you want the prices to be displayed as + + To display the output for GB and to view prices in USD, add [ countryCode=GB ] and [ currency=USD ] + as seen below: + oemsecretsapi.com/partsearch?apiKey=abcexampleapikey123&searchTerm=bd04&countryCode=GB¤cy=USD + + To view prices in both USD and GBP add [ currency[]=USD¤cy[]=GBP ] + oemsecretsapi.com/partsearch?searchTerm=bd04&apiKey=abcexampleapikey123¤cy[]=USD¤cy[]=GBP + + */ + + + // Activate this block when querying the real APIs + //------------------ + + $response = $this->oemsecretsClient->request('GET', self::ENDPOINT_URL, [ + 'query' => [ + 'searchTerm' => $keyword, + 'apiKey' => $this->api_key, + 'currency' => $this->currency, + 'countryCode' => $this->country_code, + ], + ]); + + $response_array = $response->toArray(); + //------------------*/ + + // Or activate this block when we use json file for debugging + /*/------------------ + $jsonFilePath = ''; + if (!file_exists($jsonFilePath)) { + throw new \Exception("JSON file not found."); + } + $jsonContent = file_get_contents($jsonFilePath); + $response_array = json_decode($jsonContent, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new \Exception("JSON file decode failed: " . json_last_error_msg()); + } + //------------------*/ + + $products = $response_array['stock'] ?? []; + + $results = []; + $basicInfoResults = []; + $datasheetsResults = []; + $imagesResults = []; + $parametersResults = []; + $purchaseInfoResults = []; + + foreach ($products as $product) { + if (!isset($product['part_number'], $product['manufacturer'])) { + continue; // Skip invalid product entries + } + $provider_id = $this->generateProviderId($product['part_number'], $product['manufacturer']); + + $partDetailDTO = $this->processBatch( + $product, + $provider_id, + $basicInfoResults, + $datasheetsResults, + $imagesResults, + $parametersResults, + $purchaseInfoResults + ); + + if ($partDetailDTO !== null) { + $results[$provider_id] = $partDetailDTO; + $cacheKey = $this->getCacheKey($provider_id); + $cacheItem = $this->partInfoCache->getItem($cacheKey); + $cacheItem->set($partDetailDTO); + $cacheItem->expiresAfter(3600 * 24); + $this->partInfoCache->save($cacheItem); + } + } + + //Force garbage collection to free up memory + gc_collect_cycles(); + + // Sort of the results + $this->sortResultsData($results, $keyword); + + //Force garbage collection to free up memory + gc_collect_cycles(); + + return $results; + + } + + /** + * Generates a cache key for storing part details based on the provided provider ID. + * + * This method creates a unique cache key by prefixing the provider ID with 'part_details_' + * and hashing the provider ID using MD5 to ensure a consistent and compact key format. + * + * @param string $provider_id The unique identifier of the provider or part. + * @return string The generated cache key. + */ + private function getCacheKey(string $provider_id): string { + return 'oemsecrets_part_' . md5($provider_id); + } + + + /** + * Retrieves detailed information about the part with the given provider ID from the cache. + * + * This method checks the cache for the details of the specified part. If the details are + * found in the cache, they are returned. If not, an exception is thrown indicating that + * the details could not be found. + * + * @param string $id The unique identifier of the provider or part. + * @return PartDetailDTO The detailed information about the part. + * + * @throws \Exception If no details are found for the given provider ID. + */ + public function getDetails(string $id): PartDetailDTO + { + $cacheKey = $this->getCacheKey($id); + $cacheItem = $this->partInfoCache->getItem($cacheKey); + + if ($cacheItem->isHit()) { + return $cacheItem->get(); + } + //If we have no cached result yet, we extract the part number (first part of our ID) and search for it + $partNumber = explode('|', $id)[0]; + + //The searchByKeyword method will write the results to cache, so we can just try it again afterwards + $this->searchByKeyword($partNumber); + + $cacheItem = $this->partInfoCache->getItem($cacheKey); + if ($cacheItem->isHit()) { + return $cacheItem->get(); + } + + // If the details still are not found in the cache, throw an exception + throw new \RuntimeException("Details not found for provider_id $id"); + } + + + /** + * A list of capabilities this provider supports (which kind of data it can provide). + * Not every part have to contain all of these data, but the provider should be able to provide them in general. + * Currently, this list is purely informational and not used in functional checks. + * @return ProviderCapabilities[] + */ + public function getCapabilities(): array + { + return [ + ProviderCapabilities::BASIC, + ProviderCapabilities::PICTURE, + ProviderCapabilities::DATASHEET, + ProviderCapabilities::PRICE, + ]; + } + + + /** + * Processes a single product and updates arrays for basic information, datasheets, images, parameters, + * and purchase information. Aggregates and organizes data received for a specific `part_number` and `manufacturer_id`. + * Distributors within the product are also sorted based on country_code and region. + * + * @param array $product The product data received from the OEMSecrets API. + * @param string $provider_id A string that contains the unique key created for the part + * @param array &$basicInfoResults Array containing the basic product information (e.g., name, description, category). + * @param array &$datasheetsResults Array containing datasheets collected from various distributors for the product. + * @param array &$imagesResults Array containing images of the product collected from various distributors. + * @param array &$parametersResults Array containing technical parameters extracted from the product descriptions. + * @param array &$purchaseInfoResults Array containing purchase information, including distributors and pricing details. + * + * @return PartDetailDTO|null Returns a PartDetailDTO object if the product is processed successfully, otherwise null. + * + * @throws \Exception If a required key in the product data is missing or if there is an issue creating the DTO. + * + * @see createOrUpdateBasicInfo() Creates or updates the basic product information. + * @see getPrices() Extracts the pricing information for the product. + * @see parseDataSheets() Parses and prevents duplication of datasheets. + * @see getImages() Extracts and avoids duplication of images. + * @see getParameters() Extracts technical parameters from the product description. + * @see createPurchaseInfoDTO() Creates a PurchaseInfoDTO containing distributor and price information. + * + * @note Distributors within the product are sorted by country_code and region: + * 1. Distributors with the environment's country_code come first. + * 2. Distributors in the same region as the environment's country_code are next, + * with "Global" ('XX') prioritized within this region. + * 3. Distributors with null country_code or region are placed last. + * 4. Remaining distributors are sorted alphabetically by region and country_code. + + */ + private function processBatch( + array $product, + string $provider_id, + array &$basicInfoResults, + array &$datasheetsResults, + array &$imagesResults, + array &$parametersResults, + array &$purchaseInfoResults + ): ?PartDetailDTO + { + if (!isset($product['manufacturer'], $product['part_number'])) { + throw new \InvalidArgumentException("Missing required product data: 'manufacturer' or 'part_number'"); + } + + // Retrieve the country_code associated with the distributor and store it in the $distributorCountryCodes array. + $distributorCountry = $product['distributor']['distributor_country'] ?? null; + $distributorName = $product['distributor']['distributor_name'] ?? null; + $distributorRegion = $product['distributor']['distributor_region'] ?? null; + + if ($distributorCountry && $distributorName) { + $countryCode = $this->mapCountryNameToCode($distributorCountry); + if ($countryCode) { + $this->distributorCountryCodes[$distributorName] = $countryCode; + } + if ($distributorRegion) { + $this->countryCodeToRegionMap[$countryCode] = $distributorRegion; + } + } + + // Truncate the description and handle notes + $thenotes = ''; + $description = $product['description'] ?? ''; + if (strlen($description) > 100) { + $thenotes = $description; // Save the complete description + $description = substr($description, 0, 100) . '...'; // Truncate the description + } + + // Extract prices + $priceDTOs = $this->getPrices($product); + if (empty($priceDTOs) && (int)$this->zero_price === 0) { + return null; // Skip products without valid prices + } + + $existingBasicInfo = isset($basicInfoResults[$provider_id]) && is_array($basicInfoResults[$provider_id]) + ? $basicInfoResults[$provider_id] + : []; + + $basicInfoResults[$provider_id] = $this->createOrUpdateBasicInfo( + $provider_id, + $product, + $description, + $thenotes, + $existingBasicInfo + ); + + // Update images, datasheets, and parameters + + $newDatasheets = $this->parseDataSheets($product['datasheet_url'] ?? null, null, $datasheetsResults[$provider_id] ?? []); + if ($newDatasheets !== null) { + $datasheetsResults[$provider_id] = array_merge($datasheetsResults[$provider_id] ?? [], $newDatasheets); + } + + $imagesResults[$provider_id] = $this->getImages($product, $imagesResults[$provider_id] ?? []); + if ($this->set_param == 1) { + $parametersResults[$provider_id] = $this->getParameters($product, $parametersResults[$provider_id] ?? []); + } else { + $parametersResults[$provider_id] = []; + } + + // Handle purchase information + $currentDistributor = $this->createPurchaseInfoDTO($product, $priceDTOs, $purchaseInfoResults[$provider_id] ?? []); + if ($currentDistributor !== null) { + $purchaseInfoResults[$provider_id][] = $currentDistributor; + } + + // If there is data in $purchaseInfoResults, sort it before creating the PartDetailDTO + if (!empty($purchaseInfoResults[$provider_id])) { + usort($purchaseInfoResults[$provider_id], function ($a, $b) { + $nameA = $a->distributor_name; + $nameB = $b->distributor_name; + + $countryCodeA = $this->distributorCountryCodes[$nameA] ?? null; + $countryCodeB = $this->distributorCountryCodes[$nameB] ?? null; + + $regionA = $this->countryCodeToRegionMap[$countryCodeA] ?? ''; + $regionB = $this->countryCodeToRegionMap[$countryCodeB] ?? ''; + + // If the map is empty or doesn't contain the key for $this->country_code, assign a placeholder region. + $regionForEnvCountry = $this->countryCodeToRegionMap[$this->country_code] ?? ''; + + // Convert to string before comparison to avoid mixed types + $countryCodeA = (string) $countryCodeA; + $countryCodeB = (string) $countryCodeB; + $regionA = (string) $regionA; + $regionB = (string) $regionB; + + + // Step 0: If either country code is null, place it at the end + if ($countryCodeA === '' || $regionA === '') { + return 1; // Metti A dopo B + } elseif ($countryCodeB === '' || $regionB === '') { + return -1; // Metti B dopo A + } + + // Step 1: country_code from the environment + if ($countryCodeA === $this->country_code && $countryCodeB !== $this->country_code) { + return -1; + } elseif ($countryCodeA !== $this->country_code && $countryCodeB === $this->country_code) { + return 1; + } + + // Step 2: Sort by environment's region, prioritizing "Global" (XX) + if ($regionA === $regionForEnvCountry && $regionB !== $regionForEnvCountry) { + return -1; + } elseif ($regionA !== $regionForEnvCountry && $regionB === $regionForEnvCountry) { + return 1; + } + + // Step 3: If regions are the same, prioritize "Global" (XX) + if ($regionA === $regionB) { + if ($countryCodeA === 'XX' && $countryCodeB !== 'XX') { + return -1; + } elseif ($countryCodeA !== 'XX' && $countryCodeB === 'XX') { + return 1; + } + } + + // Step 4: Alphabetical sorting by region and country_code + $regionComparison = strcasecmp($regionA , $regionB); + if ($regionComparison !== 0) { + return $regionComparison; + } + + // Alphabetical sorting as a fallback + return strcasecmp($countryCodeA, $countryCodeB); + }); + } + // Convert the gathered data into a PartDetailDTO + + $partDetailDTO = new PartDetailDTO( + provider_key: $basicInfoResults[$provider_id]['provider_key'], + provider_id: $provider_id, + name: $basicInfoResults[$provider_id]['name'], + description: $basicInfoResults[$provider_id]['description'], + category: $basicInfoResults[$provider_id]['category'], + manufacturer: $basicInfoResults[$provider_id]['manufacturer'], + mpn: $basicInfoResults[$provider_id]['mpn'], + preview_image_url: $basicInfoResults[$provider_id]['preview_image_url'], + manufacturing_status: $basicInfoResults[$provider_id]['manufacturing_status'], + provider_url: $basicInfoResults[$provider_id]['provider_url'], + footprint: $basicInfoResults[$provider_id]['footprint'] ?? null, + notes: $basicInfoResults[$provider_id]['notes'] ?? null, + datasheets: $datasheetsResults[$provider_id] ?? [], + images: $imagesResults[$provider_id] ?? [], + parameters: $parametersResults[$provider_id] ?? [], + vendor_infos: $purchaseInfoResults[$provider_id] ?? [] + ); + + return $partDetailDTO; + } + + + /** + * Extracts pricing information from the product data, converts it to PriceDTO objects, + * and returns them as an array. + * + * @param array{ + * prices?: array>, + * source_currency?: string + * } $product The product data from the OEMSecrets API containing price details. + * + * @return PriceDTO[] Array of PriceDTO objects representing different price tiers for the product. + */ + private function getPrices(array $product): array + { + $prices = $product['prices'] ?? []; + $sourceCurrency = $product['source_currency'] ?? null; + $priceDTOs = []; + + // Flag to check if we have added prices in the preferred currency + $foundPreferredCurrency = false; + + if (is_array($prices)) { + // Step 1: Check if prices exist in the preferred currency + if (isset($prices[$this->currency]) && is_array($prices[$this->currency])) { + $priceDetails = $prices[$this->currency]; + foreach ($priceDetails as $priceDetail) { + if ( + is_array($priceDetail) && + isset($priceDetail['unit_break'], $priceDetail['unit_price']) && + is_numeric($priceDetail['unit_break']) && + is_string($priceDetail['unit_price']) && + $priceDetail['unit_price'] !== "0.0000" + ) { + $priceDTOs[] = new PriceDTO( + minimum_discount_amount: (float)$priceDetail['unit_break'], + price: (string)$priceDetail['unit_price'], + currency_iso_code: $this->currency, + includes_tax: false, + price_related_quantity: 1.0 + ); + $foundPreferredCurrency = true; + } + } + } + + // Step 2: If no prices in the preferred currency, use source currency + if (!$foundPreferredCurrency && $sourceCurrency && isset($prices[$sourceCurrency]) && is_array($prices[$sourceCurrency])) { + $priceDetails = $prices[$sourceCurrency]; + foreach ($priceDetails as $priceDetail) { + if ( + is_array($priceDetail) && + isset($priceDetail['unit_break'], $priceDetail['unit_price']) && + is_numeric($priceDetail['unit_break']) && + is_string($priceDetail['unit_price']) && + $priceDetail['unit_price'] !== "0.0000" + ) { + $priceDTOs[] = new PriceDTO( + minimum_discount_amount: (float)$priceDetail['unit_break'], + price: (string)$priceDetail['unit_price'], + currency_iso_code: $sourceCurrency, + includes_tax: false, + price_related_quantity: 1.0 + ); + } + } + } + } + + return $priceDTOs; + } + + + /** + * Retrieves product images provided by the distributor. Prevents duplicates based on the image name. + * @param array{ + * image_url?: string + * } $product The product data from the OEMSecrets API containing image URLs. + * @param FileDTO[] $existingImages Optional. Existing images for the product to avoid duplicates. + * + * @return FileDTO[] Array of FileDTO objects representing the product images. + */ + private function getImages(array $product, array $existingImages = []): array + { + $images = $existingImages; + $imageUrl = $product['image_url'] ?? null; + + if ($imageUrl) { + $imageName = basename(parse_url($imageUrl, PHP_URL_PATH)); + if (!in_array($imageName, array_column($images, 'name'), true)) { + $images[] = new FileDTO(url: $imageUrl, name: $imageName); + } + } + return $images; + } + + /** + * Extracts technical parameters from the product description, ensures no duplicates, and returns them as an array. + * + * @param array{ + * description?: string + * } $product The product data from the OEMSecrets API containing product descriptions. + * @param ParameterDTO[] $existingParameters Optional. Existing parameters for the product to avoid duplicates. + * + * @return ParameterDTO[] Array of ParameterDTO objects representing technical parameters extracted from the product description. + */ + private function getParameters(array $product, array $existingParameters = []): array + { + $parameters = $existingParameters; + $description = $product['description'] ?? ''; + + // Logic to extract parameters from the description + $extractedParameters = $this->parseDescriptionToParameters($description) ?? []; + + // Ensure that $extractedParameters is an array + if (!is_array($extractedParameters)) { + $extractedParameters = []; + } + + foreach ($extractedParameters as $newParam) { + $isDuplicate = false; + foreach ($parameters as $existingParam) { + if ($existingParam->name === $newParam->name) { + $isDuplicate = true; + break; + } + } + if (!$isDuplicate) { + $parameters[] = $newParam; + } + } + + return $parameters; + } + + /** + * Creates a PurchaseInfoDTO object containing distributor and pricing information for a product. + * Ensures that the distributor name is valid and prices are available. + * + * @param array{ + * distributor?: array{ + * distributor_name?: string + * }, + * sku?: string, + * source_part_number: string, + * buy_now_url?: string, + * lead_time_weeks?: mixed + * } $product The product data from the OEMSecrets API. + * @param PriceDTO[] $priceDTOs Array of PriceDTO objects representing pricing tiers. + * @param PurchaseInfoDTO[] $existingPurchaseInfos Optional. Existing purchase information for the product to avoid duplicates. + * + * @return PurchaseInfoDTO|null A PurchaseInfoDTO object containing the distributor information, or null if invalid. + */ + private function createPurchaseInfoDTO(array $product, array $priceDTOs, array $existingPurchaseInfos = []): ?PurchaseInfoDTO + { + $distributor_name = $product['distributor']['distributor_name'] ?? null; + if ($distributor_name && !empty($priceDTOs)) { + $sku = isset($product['sku']) ? (string)$product['sku'] : null; + $order_number_base = $sku ?: (string)$product['source_part_number']; + $order_number = $order_number_base; + + // Remove duplicates from the quantity/price tiers + $uniquePriceDTOs = []; + foreach ($priceDTOs as $priceDTO) { + $key = $priceDTO->minimum_discount_amount . '-' . $priceDTO->price; + $uniquePriceDTOs[$key] = $priceDTO; + } + $priceDTOs = array_values($uniquePriceDTOs); + + // Differentiate $order_number if duplicated + if ($this->isDuplicateOrderNumber($order_number, $distributor_name, $existingPurchaseInfos)) { + $lead_time_weeks = isset($product['lead_time_weeks']) ? (string)$product['lead_time_weeks'] : ''; + $order_number = $order_number_base . '-' . $lead_time_weeks; + + // If there is still a duplicate after adding lead_time_weeks + $counter = 1; + while ($this->isDuplicateOrderNumber($order_number, $distributor_name, $existingPurchaseInfos)) { + $order_number = $order_number_base . '-' . $lead_time_weeks . '-' . $counter; + $counter++; + } + } + + return new PurchaseInfoDTO( + distributor_name: $distributor_name, + order_number: $order_number, + prices: $priceDTOs, + product_url: $this->unwrapURL($product['buy_now_url'] ?? null) + ); + } + return null; // Return null if no valid distributor exists + } + + /** + * Checks if an order number already exists for a given distributor in the existing purchase infos. + * + * @param string $order_number The order number to check. + * @param string $distributor_name The name of the distributor. + * @param PurchaseInfoDTO[] $existingPurchaseInfos The existing purchase information to check against. + * @return bool True if a duplicate order number is found, otherwise false. + */ + private function isDuplicateOrderNumber(string $order_number, string $distributor_name, array $existingPurchaseInfos): bool + { + foreach ($existingPurchaseInfos as $purchaseInfo) { + if ($purchaseInfo->distributor_name === $distributor_name && $purchaseInfo->order_number === $order_number) { + return true; + } + } + return false; + } + + /** + * Creates or updates the basic information of a product, including the description, category, manufacturer, + * and other metadata. This function manages the PartDetailDTO creation or update. + * + * @param string $provider_id The unique identifier for the product based on part_number and manufacturer. + * * @param array{ + * part_number: string, + * category: string, + * manufacturer: string, + * source_part_number: string, + * image_url?: string, + * life_cycle?: string, + * quantity_in_stock?: int + * } $product The product data from the OEMSecrets API. + * @param string $description The truncated description for the product. + * @param string $thenotes The full description saved as notes for the product. + * + * @return array The updated or newly created PartDetailDTO containing basic product information. + */ + private function createOrUpdateBasicInfo( + string $provider_id, + array $product, + string $description, + string $thenotes, + ?array $existingBasicInfo + ): array { + // If there is no existing basic info array, we create a new one + if (is_null($existingBasicInfo)) { + return [ + 'provider_key' => $this->getProviderKey(), + 'provider_id' => $provider_id, + 'name' => $product['part_number'], + 'description' => $description, + 'category' => $product['category'], + 'manufacturer' => $product['manufacturer'], + 'mpn' => $product['source_part_number'], + 'preview_image_url' => $product['image_url'] ?? null, + 'manufacturing_status' => $this->releaseStatusCodeToManufacturingStatus( + $product['life_cycle'] ?? null, + (int)($product['quantity_in_stock'] ?? 0) + ), + 'provider_url' => $this->generateInquiryUrl($product['part_number']), + 'notes' => $thenotes, + 'footprint' => null + ]; + } + + // Update fields only if empty or undefined, with additional check for preview_image_url + return [ + 'provider_key' => $existingBasicInfo['provider_key'] ?? $this->getProviderKey(), + 'provider_id' => $existingBasicInfo['provider_id'] ?? $provider_id, + 'name' => $existingBasicInfo['name'] ?? $product['part_number'], + // Update description if it's null/empty + 'description' => !empty($existingBasicInfo['description']) + ? $existingBasicInfo['description'] + : $description, + // Update category if it's null/empty + 'category' => !empty($existingBasicInfo['category']) + ? $existingBasicInfo['category'] + : $product['category'], + 'manufacturer' => $existingBasicInfo['manufacturer'] ?? $product['manufacturer'], + 'mpn' => $existingBasicInfo['mpn'] ?? $product['source_part_number'], + 'preview_image_url' => !empty($existingBasicInfo['preview_image_url']) + ? $existingBasicInfo['preview_image_url'] + : ($product['image_url'] ?? null), + 'manufacturing_status' => !empty($existingBasicInfo['manufacturing_status']) + ? $existingBasicInfo['manufacturing_status'] + : $this->releaseStatusCodeToManufacturingStatus( + $product['life_cycle'] ?? null, + (int)($product['quantity_in_stock'] ?? 0) + ), + 'provider_url' => $existingBasicInfo['provider_url'] ?? $this->generateInquiryUrl($product['part_number']), // ?? $product['buy_now_url'], + 'notes' => $existingBasicInfo['notes'] ?? $thenotes, + 'footprint' => null + ]; + } + + /** + * Parses the datasheet URL and returns an array of FileDTO objects representing the datasheets. + * If the datasheet name is not provided, it attempts to extract the file name from the URL. + * If multiple datasheets with the same default name are encountered, the function appends a + * numeric suffix to ensure uniqueness. + * The query parameter used to extract the event link can be customized. + * + * URL Requirements: + * - The URL should be a valid URL string. + * - The URL can include a query parameter named "event_link", which contains a sub-URL where the + * actual datasheet file name is located (e.g., a link to a PDF file). + * - If "event_link" is not present, the function attempts to extract the file name directly from + * the URL path. + * - The URL path should ideally end with a valid file extension (e.g., .pdf, .doc, .xls, etc.). + * + * Example 1: + * Given URL: `https://example.com/datasheet.php?event_link=https%3A%2F%2Ffiles.example.com%2Fdatasheet.pdf` + * Extracted name: `datasheet.pdf` + * + * Example 2: + * Given URL: `https://example.com/files/datasheet.pdf` + * Extracted name: `datasheet.pdf` + * + * Example 3 (default name fallback): + * Given URL: `https://example.com/files/noextensionfile` + * Extracted name: `datasheet.pdf` + * + * @param string|null $sheetUrl The URL of the datasheet. + * @param string|null $sheetName The optional name of the datasheet. If null, the name is extracted from the URL. + * @param array $existingDatasheets The array of existing datasheets to check for duplicates. + * + * @return FileDTO[]|null Returns an array containing the new datasheet if unique, or null if the datasheet is a duplicate or invalid. + * + * @see FileDTO Used to create datasheet objects with a URL and name. + */ + private function parseDataSheets(?string $sheetUrl, ?string $sheetName, array $existingDatasheets = []): ?array + { + if ($sheetUrl === null || $sheetUrl === '' || $sheetUrl === '0') { + return null; + } + + //Unwrap the URL (remove analytics part) + $sheetUrl = $this->unwrapURL($sheetUrl); + + // If the datasheet name is not provided, extract it from the URL + if ($sheetName === null) { + $urlPath = parse_url($sheetUrl, PHP_URL_PATH); + if ($urlPath === false) { + throw new \RuntimeException("Invalid URL path: $sheetUrl"); + } + + // If "event_link" does not exist, try to extract the name from the main URL path + $sheetName = basename($urlPath); + if (!str_contains($sheetName, '.') || !preg_match('/\.(pdf|doc|docx|xls|xlsx|ppt|pptx)$/i', $sheetName)) { + // If the name does not have a valid extension, assign a default name + $sheetName = 'datasheet_' . uniqid('', true) . '.pdf'; + } + } + + // Create an array of existing file names + $existingNames = array_map(static function ($existingDatasheet) { + return $existingDatasheet->name; + }, $existingDatasheets); + + // Check if the name already exists + if (in_array($sheetName, $existingNames, true)) { + // The name already exists, so do not add the datasheet + return null; + } + + // Create an array with the datasheet data if it does not already exist + $result = []; + $result[] = new FileDTO(url: $sheetUrl, name: $sheetName); + return $result; + } + + /** + * Converts the lifecycle status from the API to a ManufacturingStatus + * - "Factory Special Order" / "Ordine speciale in fabbrica" + * - "Not Recommended for New Designs" / "Non raccomandato per nuovi progetti" + * - "New Product" / "Nuovo prodotto" (if availableInStock > 0 else ANNOUNCED) + * - "End of Life" / "Fine vita" + * - vuoto / "Attivo" + * + * @param string|null $productStatus The lifecycle status from the Mouser API. Expected values are: + * - "Factory Special Order" + * - "Not Recommended for New Designs" + * - "New Product" + * - "End of Life" + * - "Obsolete" + * @param int $availableInStock The number of parts available in stock. + * @return ManufacturingStatus|null Returns the corresponding ManufacturingStatus or null if the status is unknown. + * + * @todo Probably need to review the values of field Lifecyclestatus. + */ + private function releaseStatusCodeToManufacturingStatus(?string $productStatus, int $availableInStock = 0): ?ManufacturingStatus + { + $tmp = match ($productStatus) { + null => null, + "New Product" => ManufacturingStatus::ANNOUNCED, + "Not Recommended for New Designs" => ManufacturingStatus::NRFND, + "Factory Special Order", "Obsolete" => ManufacturingStatus::DISCONTINUED, + "End of Life" => ManufacturingStatus::EOL, + default => null, //ManufacturingStatus::ACTIVE, + }; + + //If the part would be assumed to be announced, check if it is in stock, then it is active + if ($tmp === ManufacturingStatus::ANNOUNCED && $availableInStock > 0) { + $tmp = ManufacturingStatus::ACTIVE; + } + + return $tmp; + } + + /** + * Parses the given product description to extract parameters and convert them into `ParameterDTO` objects. + * If the description contains only a single `:`, it is considered unstructured and ignored. + * The function processes the description by searching for key-value pairs in the format `name: value`, + * ignoring any parts of the description that do not follow this format. Parameters are split using either + * `;` or `,` as separators. + * + * The extraction logic handles typical values, ranges, units, and textual information from the description. + * If the description is empty or cannot be processed into valid parameters, the function returns null. + * + * @param string|null $description The description text from which parameters are to be extracted. + * + * @return ParameterDTO[]|null Returns an array of `ParameterDTO` objects if parameters are successfully extracted, + * or null if no valid parameters can be extracted from the description. + */ + private function parseDescriptionToParameters(?string $description): ?array + { + // If the description is null or empty, return null + if ($description === null || trim($description) === '') { + return null; + } + + // If the description contains only a single ':', return null + if (substr_count($description, ':') === 1) { + return null; + } + + // Array to store parsed parameters + $parameters = []; + + // Split the description using the ';' separator + $parts = preg_split('/[;,]/', $description); //explode(';', $description); + + // Process each part of the description + foreach ($parts as $part) { + $part = trim($part); + + // Check if the part contains a key-value structure + if (str_contains($part, ':')) { + [$name, $value] = explode(':', $part, 2); + $name = trim($name); + $value = trim($value); + + // Attempt to parse the value, handling ranges, units, and additional information + $parsedValue = $this->customParseValueIncludingUnit($name, $value); + + // If the value was successfully parsed, create a ParameterDTO + if ($parsedValue) { + // Convert numeric values to float + $value_typ = isset($parsedValue['value_typ']) ? (float)$parsedValue['value_typ'] : null; + $value_min = isset($parsedValue['value_min']) ? (float)$parsedValue['value_min'] : null; + $value_max = isset($parsedValue['value_max']) ? (float)$parsedValue['value_max'] : null; + + $parameters[] = new ParameterDTO( + name: $parsedValue['name'], + value_text: $parsedValue['value_text'] ?? null, + value_typ: $value_typ, + value_min: $value_min, + value_max: $value_max, + unit: $parsedValue['unit'] ?? null, // Add extracted unit + symbol: $parsedValue['symbol'] ?? null // Add extracted symbol + ); + } + } + } + + return !empty($parameters) ? $parameters : null; + } + + /** + * Parses a value that may contain both a numerical value and its corresponding unit. + * This function splits the value into its numerical part and its unit, handling cases + * where the value includes symbols, ranges, or additional text. It also detects and + * processes plus/minus ranges, typical values, and other special formats. + * + * Example formats that can be handled: + * - "2.5V" + * - "±5%" + * - "1-10A" + * - "2.5 @text" + * - "~100 Ohm" + * + * @param string $value The value string to be parsed, which may contain a number, unit, or both. + * + * @return array An associative array with parsed components: + * - 'name' => string (the name of the parameter) + * - 'value_typ' => float|null (the typical or parsed value) + * - 'range_min' => float|null (the minimum value if it's a range) + * - 'range_max' => float|null (the maximum value if it's a range) + * - 'value_text' => string|null (any additional text or symbol) + * - 'unit' => string|null (the detected or default unit) + * - 'symbol' => string|null (any special symbol or additional text) + */ + private function customParseValueIncludingUnit(string $name, string $value): array + { + // Parse using logic for units, ranges, and other elements + $result = [ + 'name' => $name, + 'value_typ' => null, + 'value_min' => null, + 'value_max' => null, + 'value_text' => null, + 'unit' => null, + 'symbol' => null, + ]; + + // Trim any whitespace from the value + $value = trim($value); + + // Handle ranges and plus/minus signs + if (str_contains($value, '...') || str_contains($value, '~') || str_contains($value, '±')) { + // Handle ranges + $value = str_replace(['...', '~'], '...', $value); // Normalize range separators + $rangeParts = preg_split('/\s*[\.\~]\s*/', $value); + + if (count($rangeParts) === 2) { + // Splitting the values and units + $parsedMin = $this->customSplitIntoValueAndUnit($rangeParts[0]); + $parsedMax = $this->customSplitIntoValueAndUnit($rangeParts[1]); + + // Assigning the parsed values + $result['value_min'] = $parsedMin['value_typ']; + $result['value_max'] = $parsedMax['value_typ']; + + // Determine the unit + $result['unit'] = $parsedMax['unit'] ?? $parsedMin['unit']; + } + + } elseif (str_contains($value, '@')) { + // If we find "@", we treat it as additional textual information + [$numericValue, $textValue] = explode('@', $value); + $result['value_typ'] = (float) $numericValue; + $result['value_text'] = trim($textValue); + } else { + // Check if the value is numeric with a unit + if (preg_match('/^([\+\-]?\d+(\.\d+)?)([a-zA-Z%°]+)?$/u', $value, $matches)) { + // It is a number with or without a unit + $result['value_typ'] = (float) $matches[1]; + $result['unit'] = $matches[3] ?? null; + } else { + // It's not a number, so we treat it as text + $result['value_text'] = $value; + } + } + + return $result; + } + + /** + * Splits a string into a numerical value and its associated unit. The function attempts to separate + * a number from its unit, handling common formats where the unit follows the number (e.g., "50kHz", "10A"). + * The function assumes the unit is the non-numeric part of the string. + * + * Example formats that can be handled: + * - "100 Ohm" + * - "10 MHz" + * - "5kV" + * - "±5%" + * + * @param string $value1 The input string containing both a numerical value and a unit. + * @param string|null $value2 Optional. A second value string, typically used for ranges (e.g., "10-20A"). + * + * @return array An associative array with the following elements: + * - 'value_typ' => string|null The first numerical part of the string. + * - 'unit' => string|null The unit part of the string, or null if no unit is detected. + * - 'value_min' => string|null The minimum value in a range, if applicable. + * - 'value_max' => string|null The maximum value in a range, if applicable. + */ + private function customSplitIntoValueAndUnit(string $value1, string $value2 = null): array + { + // Separate numbers and units (basic parsing handling) + $unit = null; + $value_typ = null; + + // Search for the number + unit pattern + if (preg_match('/^([\+\-]?\d+(\.\d+)?)([a-zA-Z%°]+)?$/u', $value1, $matches)) { + $value_typ = $matches[1]; + $unit = $matches[3] ?? null; + } + + $result = [ + 'value_typ' => $value_typ, + 'unit' => $unit, + ]; + + if ($value2 !== null) { + if (preg_match('/^([\+\-]?\d+(\.\d+)?)([a-zA-Z%°]+)?$/u', $value2, $matches2)) { + $result['value_min'] = $value_typ; + $result['value_max'] = $matches2[1]; + $result['unit'] = $matches2[3] ?? $unit; // If both values have the same unit, we keep it + } + } + + return $result; + } + + /** + * Generates the API URL to fetch product information for the specified part number from OEMSecrets. + * Ensures that the base API URL and any query parameters are properly formatted. + * + * @param string $partNumber The part number to include in the URL. + * @param string $oemInquiry The inquiry path for the OEMSecrets API, with a default value of 'compare/'. + * This parameter represents the specific API endpoint to query. + * + * @return string The complete provider URL including the base provider URL, the inquiry path, and the part number. + * + * Example: + * If the base URL is "https://www.oemsecrets.com/", the inquiry path is "compare/", and the part number is "NE555", + * the resulting URL will be: "https://www.oemsecrets.com/compare/NE555" + */ + private function generateInquiryUrl(string $partNumber, string $oemInquiry = 'compare/'): string + { + $baseUrl = rtrim($this->getProviderInfo()['url'], '/') . '/'; + $inquiryPath = trim($oemInquiry, '/') . '/'; + $encodedPartNumber = urlencode(trim($partNumber)); + return $baseUrl . $inquiryPath . $encodedPartNumber; + } + + /** + * Sorts the results data array based on the specified search keyword and sorting criteria. + * The sorting process involves multiple phases: + * 1. Exact match with the search keyword. + * 2. Prefix match with the search keyword. + * 3. Alphabetical order of the suffix following the keyword. + * 4. Optional sorting by completeness or manufacturer based on the sort criteria. + * + * The sorting criteria (`sort_criteria`) is an environment variable configured in the `.env.local` file: + * PROVIDER_OEMSECRETS_SORT_CRITERIA + * It determines the final sorting phase: + * - 'C': Sort by completeness. + * - 'M': Sort by manufacturer. + * + * @param array $resultsData The array of result objects to be sorted. Each object should have 'name' and 'manufacturer' properties. + * @param string $searchKeyword The search keyword used for sorting the results. + * + * @return void + */ + private function sortResultsData(array &$resultsData, string $searchKeyword): void + { + // If the SORT_CRITERIA is not 'C' or 'M', do not sort + if ($this->sort_criteria !== 'C' && $this->sort_criteria !== 'M') { + return; + } + usort($resultsData, function ($a, $b) use ($searchKeyword) { + $nameA = trim($a->name); + $nameB = trim($b->name); + + // First phase: Sorting by exact match with the keyword + $exactMatchA = strcasecmp($nameA, $searchKeyword) === 0; + $exactMatchB = strcasecmp($nameB, $searchKeyword) === 0; + + if ($exactMatchA && !$exactMatchB) { + return -1; + } elseif (!$exactMatchA && $exactMatchB) { + return 1; + } + + // Second phase: Sorting by prefix (name starting with the keyword) + $startsWithKeywordA = stripos($nameA, $searchKeyword) === 0; + $startsWithKeywordB = stripos($nameB, $searchKeyword) === 0; + + if ($startsWithKeywordA && !$startsWithKeywordB) { + return -1; + } elseif (!$startsWithKeywordA && $startsWithKeywordB) { + return 1; + } + + if ($startsWithKeywordA && $startsWithKeywordB) { + // Alphabetical sorting of suffixes + $suffixA = substr($nameA, strlen($searchKeyword)); + $suffixB = substr($nameB, strlen($searchKeyword)); + $suffixComparison = strcasecmp($suffixA, $suffixB); + + if ($suffixComparison !== 0) { + return $suffixComparison; + } + } + + // Final sorting: by completeness or manufacturer, if necessary + if ($this->sort_criteria === 'C') { + return $this->compareByCompleteness($a, $b); + } elseif ($this->sort_criteria === 'M') { + return strcasecmp($a->manufacturer, $b->manufacturer); + } + + }); + } + + /** + * Compares two objects based on their "completeness" score. + * The completeness score is calculated by the `calculateCompleteness` method, which assigns a numeric score + * based on the amount of information (such as parameters, datasheets, images, etc.) available for each object. + * The comparison is done in descending order, giving priority to the objects with higher completeness. + * + * @param object $a The first object to compare. + * @param object $b The second object to compare. + * + * @return int A negative value if $b is more complete than $a, zero if they are equally complete, + * or a positive value if $a is more complete than $b. + */ + private function compareByCompleteness(object $a, object $b): int + { + // Calculate the completeness score for each object + $completenessA = $this->calculateCompleteness($a); + $completenessB = $this->calculateCompleteness($b); + + // Sort in descending order by completeness (higher score is better) + return $completenessB - $completenessA; + } + + + /** + * Calculates a "completeness" score for a given part object based on the presence and count of various attributes. + * The completeness score is used to prioritize parts that have more detailed information. + * + * The score is calculated as follows: + * - Counts the number of elements in the `parameters`, `datasheets`, `images`, and `vendor_infos` arrays. + * - Adds 1 point for the presence of `category`, `description`, `mpn`, `preview_image_url`, and `footprint`. + * - Adds 1 or 2 points based on the presence or absence of `manufacturing_status` (higher score if `null`). + * + * @param object $part The part object for which the completeness score is calculated. The object is expected + * to have properties like `parameters`, `datasheets`, `images`, `vendor_infos`, `category`, + * `description`, `mpn`, `preview_image_url`, `footprint`, and `manufacturing_status`. + * + * @return int The calculated completeness score, with a higher score indicating more complete information. + */ + private function calculateCompleteness(object $part): int + { + // Counts the number of elements in each field that can have multiple values + $paramsCount = is_array($part->parameters) ? count($part->parameters) : 0; + $datasheetsCount = is_array($part->datasheets) ? count($part->datasheets) : 0; + $imagesCount = is_array($part->images) ? count($part->images) : 0; + $vendorInfosCount = is_array($part->vendor_infos) ? count($part->vendor_infos) : 0; + + // Check for the presence of single fields and assign a score + $categoryScore = !empty($part->category) ? 1 : 0; + $descriptionScore = !empty($part->description) ? 1 : 0; + $mpnScore = !empty($part->mpn) ? 1 : 0; + $previewImageScore = !empty($part->preview_image_url) ? 1 : 0; + $footprintScore = !empty($part->footprint) ? 1 : 0; + + // Weight for manufacturing_status: higher if null + $manufacturingStatusScore = is_null($part->manufacturing_status) ? 2 : 1; + + // Sum the counts and scores to obtain a completeness score + return $paramsCount + + $datasheetsCount + + $imagesCount + + $vendorInfosCount + + $categoryScore + + $descriptionScore + + $mpnScore + + $previewImageScore + + $footprintScore + + $manufacturingStatusScore; + } + + + /** + * Generates a unique provider ID by concatenating the part number and manufacturer name, + * separated by a pipe (`|`). The generated ID is typically used to uniquely identify + * a specific part from a particular manufacturer. + * + * @param string $partNumber The part number of the product. + * @param string $manufacturer The name of the manufacturer. + * + * @return string The generated provider ID, in the format "partNumber|manufacturer". + */ + private function generateProviderId(string $partNumber, string $manufacturer): string + { + return trim($partNumber) . '|' . trim($manufacturer); + } + + /** + * Maps the name of a country to its corresponding ISO 3166-1 alpha-2 code. + * + * @param string|null $countryName The name of the country to map. + * @return string|null The ISO code for the country, or null if not found. + */ + private function mapCountryNameToCode(?string $countryName): ?string + { + return $this->countryNameToCodeMap[$countryName] ?? null; + } + + /** + * Removes the analytics tracking parts from the URLs returned by the API. + * + * @param string|null $url + * @return string|null + */ + private function unwrapURL(?string $url): ?string + { + if ($url === null) { + return null; + } + + //Check if the URL is a one redirected via analytics + if (str_contains($url, 'analytics.oemsecrets.com/main.php')) { + //Extract the URL from the analytics URL + $queryParams = []; + parse_str(parse_url($url, PHP_URL_QUERY), $queryParams); + + //The real URL is stored in the 'event_link' query parameter + if (isset($queryParams['event_link']) && trim($queryParams['event_link']) !== '') { + $url = $queryParams['event_link']; + + //Replace any spaces in the URL by %20 to avoid invalid URLs + return str_replace(' ', '%20', $url); + } + } + + //Otherwise return the URL as it is + return $url; + } + +} \ No newline at end of file diff --git a/src/Services/InfoProviderSystem/Providers/OctopartProvider.php b/src/Services/InfoProviderSystem/Providers/OctopartProvider.php index f261e225..e28162ba 100644 --- a/src/Services/InfoProviderSystem/Providers/OctopartProvider.php +++ b/src/Services/InfoProviderSystem/Providers/OctopartProvider.php @@ -183,7 +183,7 @@ class OctopartProvider implements InfoProviderInterface { //The client ID has to be set and a token has to be available (user clicked connect) //return /*!empty($this->clientId) && */ $this->authTokenManager->hasToken(self::OAUTH_APP_NAME); - return !empty($this->clientId) && !empty($this->secret); + return $this->clientId !== '' && $this->secret !== ''; } private function mapLifeCycleStatus(?string $value): ?ManufacturingStatus @@ -243,11 +243,14 @@ class OctopartProvider implements InfoProviderInterface //If we encounter the mass spec, we save it for later if ($spec['attribute']['shortname'] === "weight") { $mass = (float) $spec['siValue']; - } else if ($spec['attribute']['shortname'] === "case_package") { //Package + } elseif ($spec['attribute']['shortname'] === "case_package") { + //Package $package = $spec['value']; - } else if ($spec['attribute']['shortname'] === "numberofpins") { //Pin Count + } elseif ($spec['attribute']['shortname'] === "numberofpins") { + //Pin Count $pinCount = $spec['value']; - } else if ($spec['attribute']['shortname'] === "lifecyclestatus") { //LifeCycleStatus + } elseif ($spec['attribute']['shortname'] === "lifecyclestatus") { + //LifeCycleStatus $mStatus = $this->mapLifeCycleStatus($spec['value']); } @@ -295,7 +298,7 @@ class OctopartProvider implements InfoProviderInterface $category = null; if (!empty($part['category']['name'])) { $category = implode(' -> ', array_map(static fn($c) => $c['name'], $part['category']['ancestors'] ?? [])); - if (!empty($category)) { + if ($category !== '' && $category !== '0') { $category .= ' -> '; } $category .= $part['category']['name']; diff --git a/src/Services/InfoProviderSystem/Providers/TMEClient.php b/src/Services/InfoProviderSystem/Providers/TMEClient.php index df2c7202..0e32e9a6 100644 --- a/src/Services/InfoProviderSystem/Providers/TMEClient.php +++ b/src/Services/InfoProviderSystem/Providers/TMEClient.php @@ -47,7 +47,7 @@ class TMEClient public function isUsable(): bool { - return !($this->token === '' || $this->secret === ''); + return $this->token !== '' && $this->secret !== ''; } diff --git a/src/Services/InfoProviderSystem/Providers/TMEProvider.php b/src/Services/InfoProviderSystem/Providers/TMEProvider.php index 892294f3..61944b7d 100644 --- a/src/Services/InfoProviderSystem/Providers/TMEProvider.php +++ b/src/Services/InfoProviderSystem/Providers/TMEProvider.php @@ -80,7 +80,7 @@ class TMEProvider implements InfoProviderInterface $result[] = new SearchResultDTO( provider_key: $this->getProviderKey(), provider_id: $product['Symbol'], - name: !empty($product['OriginalSymbol']) ? $product['OriginalSymbol'] : $product['Symbol'], + name: empty($product['OriginalSymbol']) ? $product['Symbol'] : $product['OriginalSymbol'], description: $product['Description'], category: $product['Category'], manufacturer: $product['Producer'], @@ -116,7 +116,7 @@ class TMEProvider implements InfoProviderInterface return new PartDetailDTO( provider_key: $this->getProviderKey(), provider_id: $product['Symbol'], - name: !empty($product['OriginalSymbol']) ? $product['OriginalSymbol'] : $product['Symbol'], + name: empty($product['OriginalSymbol']) ? $product['Symbol'] : $product['OriginalSymbol'], description: $product['Description'], category: $product['Category'], manufacturer: $product['Producer'], diff --git a/src/Services/LabelSystem/Barcodes/BarcodeHelper.php b/src/Services/LabelSystem/Barcodes/BarcodeHelper.php index d13da589..c9fe64f3 100644 --- a/src/Services/LabelSystem/Barcodes/BarcodeHelper.php +++ b/src/Services/LabelSystem/Barcodes/BarcodeHelper.php @@ -28,6 +28,7 @@ use Com\Tecnick\Barcode\Barcode; /** * This function is used to generate barcodes of various types using arbitrary (text) content. + * @see \App\Tests\Services\LabelSystem\Barcodes\BarcodeHelperTest */ class BarcodeHelper { @@ -66,7 +67,7 @@ class BarcodeHelper { $svg = $this->barcodeAsSVG($content, $type); $base64 = $this->dataUri($svg, 'image/svg+xml'); - $alt_text = $alt_text ?? $content; + $alt_text ??= $content; return ''.$alt_text.''; } diff --git a/src/Services/LabelSystem/Barcodes/BarcodeScanHelper.php b/src/Services/LabelSystem/Barcodes/BarcodeScanHelper.php index b53d3f02..c9750ea3 100644 --- a/src/Services/LabelSystem/Barcodes/BarcodeScanHelper.php +++ b/src/Services/LabelSystem/Barcodes/BarcodeScanHelper.php @@ -48,7 +48,7 @@ use Doctrine\ORM\EntityManagerInterface; use InvalidArgumentException; /** - * @see \App\Tests\Services\LabelSystem\Barcodes\BarcodeNormalizerTest + * @see \App\Tests\Services\LabelSystem\Barcodes\BarcodeScanHelperTest */ final class BarcodeScanHelper { diff --git a/src/Services/LabelSystem/DompdfFactory.php b/src/Services/LabelSystem/DompdfFactory.php index ff30c480..a2c8c3cd 100644 --- a/src/Services/LabelSystem/DompdfFactory.php +++ b/src/Services/LabelSystem/DompdfFactory.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\LabelSystem; use Dompdf\Dompdf; @@ -36,10 +38,8 @@ class DompdfFactory implements DompdfFactoryInterface private function createDirectoryIfNotExisting(string $path): void { - if (!is_dir($path)) { - if (!mkdir($concurrentDirectory = $path, 0777, true) && !is_dir($concurrentDirectory)) { - throw new \RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory)); - } + if (!is_dir($path) && (!mkdir($concurrentDirectory = $path, 0777, true) && !is_dir($concurrentDirectory))) { + throw new \RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory)); } } @@ -51,4 +51,4 @@ class DompdfFactory implements DompdfFactoryInterface 'tempDir' => $this->tmpDirectory, ]); } -} \ No newline at end of file +} diff --git a/src/Services/LabelSystem/LabelBarcodeGenerator.php b/src/Services/LabelSystem/LabelBarcodeGenerator.php index f5d950b4..66f74e58 100644 --- a/src/Services/LabelSystem/LabelBarcodeGenerator.php +++ b/src/Services/LabelSystem/LabelBarcodeGenerator.php @@ -49,7 +49,7 @@ use App\Services\LabelSystem\Barcodes\BarcodeHelper; use InvalidArgumentException; /** - * @see \App\Tests\Services\LabelSystem\BarcodeGeneratorTest + * @see \App\Tests\Services\LabelSystem\LabelBarcodeGeneratorTest */ final class LabelBarcodeGenerator { diff --git a/src/Services/LabelSystem/LabelExampleElementsGenerator.php b/src/Services/LabelSystem/LabelExampleElementsGenerator.php index c9cdbb04..199d5f46 100644 --- a/src/Services/LabelSystem/LabelExampleElementsGenerator.php +++ b/src/Services/LabelSystem/LabelExampleElementsGenerator.php @@ -97,7 +97,7 @@ final class LabelExampleElementsGenerator $lot->setDescription('Example Lot'); $lot->setComment('Lot comment'); - $lot->setExpirationDate(new DateTime('+1 days')); + $lot->setExpirationDate(new \DateTimeImmutable('+1 day')); $lot->setStorageLocation($this->getStructuralData(StorageLocation::class)); $lot->setAmount(123); $lot->setOwner($this->getUser()); diff --git a/src/Services/LabelSystem/PlaceholderProviders/GlobalProviders.php b/src/Services/LabelSystem/PlaceholderProviders/GlobalProviders.php index 5a9b2294..ddd4dbf1 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/GlobalProviders.php +++ b/src/Services/LabelSystem/PlaceholderProviders/GlobalProviders.php @@ -81,7 +81,7 @@ final class GlobalProviders implements PlaceholderProviderInterface return 'anonymous'; } - $now = new DateTime(); + $now = new \DateTimeImmutable(); if ('[[DATETIME]]' === $placeholder) { $formatter = IntlDateFormatter::create( diff --git a/src/Services/LabelSystem/PlaceholderProviders/PartProvider.php b/src/Services/LabelSystem/PlaceholderProviders/PartProvider.php index 9d9b3416..0df4d3d7 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/PartProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/PartProvider.php @@ -119,7 +119,7 @@ final class PartProvider implements PlaceholderProviderInterface } if ('[[DESCRIPTION_T]]' === $placeholder) { - return strip_tags($parsedown->line($part->getDescription())); + return strip_tags((string) $parsedown->line($part->getDescription())); } if ('[[COMMENT]]' === $placeholder) { @@ -127,7 +127,7 @@ final class PartProvider implements PlaceholderProviderInterface } if ('[[COMMENT_T]]' === $placeholder) { - return strip_tags($parsedown->line($part->getComment())); + return strip_tags((string) $parsedown->line($part->getComment())); } return null; diff --git a/src/Services/LabelSystem/PlaceholderProviders/StructuralDBElementProvider.php b/src/Services/LabelSystem/PlaceholderProviders/StructuralDBElementProvider.php index ca8088da..f37f5901 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/StructuralDBElementProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/StructuralDBElementProvider.php @@ -52,7 +52,7 @@ final class StructuralDBElementProvider implements PlaceholderProviderInterface return $label_target->getComment(); } if ('[[COMMENT_T]]' === $placeholder) { - return strip_tags($label_target->getComment()); + return strip_tags((string) $label_target->getComment()); } if ('[[FULL_PATH]]' === $placeholder) { return $label_target->getFullPath(); diff --git a/src/Services/LabelSystem/PlaceholderProviders/TimestampableElementProvider.php b/src/Services/LabelSystem/PlaceholderProviders/TimestampableElementProvider.php index 8588e133..b316abf2 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/TimestampableElementProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/TimestampableElementProvider.php @@ -42,7 +42,6 @@ declare(strict_types=1); namespace App\Services\LabelSystem\PlaceholderProviders; use App\Entity\Contracts\TimeStampableInterface; -use DateTime; use IntlDateFormatter; use Locale; @@ -57,11 +56,11 @@ final class TimestampableElementProvider implements PlaceholderProviderInterface $formatter = new IntlDateFormatter(Locale::getDefault(), IntlDateFormatter::SHORT, IntlDateFormatter::SHORT); if ('[[LAST_MODIFIED]]' === $placeholder) { - return $formatter->format($label_target->getLastModified() ?? new DateTime()); + return $formatter->format($label_target->getLastModified() ?? new \DateTimeImmutable()); } if ('[[CREATION_DATE]]' === $placeholder) { - return $formatter->format($label_target->getAddedDate() ?? new DateTime()); + return $formatter->format($label_target->getAddedDate() ?? new \DateTimeImmutable()); } } diff --git a/src/Services/LabelSystem/SandboxedTwigFactory.php b/src/Services/LabelSystem/SandboxedTwigFactory.php index 3e5ab7e7..cdf0594f 100644 --- a/src/Services/LabelSystem/SandboxedTwigFactory.php +++ b/src/Services/LabelSystem/SandboxedTwigFactory.php @@ -71,6 +71,7 @@ use App\Twig\TwigCoreExtension; use InvalidArgumentException; use Twig\Environment; use Twig\Extension\SandboxExtension; +use Twig\Extra\Html\HtmlExtension; use Twig\Extra\Intl\IntlExtension; use Twig\Extra\Markdown\MarkdownExtension; use Twig\Extra\String\StringExtension; @@ -183,6 +184,7 @@ final class SandboxedTwigFactory $twig->addExtension(new IntlExtension()); $twig->addExtension(new MarkdownExtension()); $twig->addExtension(new StringExtension()); + $twig->addExtension(new HtmlExtension()); //Add Part-DB specific extension $twig->addExtension($this->formatExtension); diff --git a/src/Services/LogSystem/EventUndoMode.php b/src/Services/LogSystem/EventUndoMode.php index 51ad664e..de30dcfd 100644 --- a/src/Services/LogSystem/EventUndoMode.php +++ b/src/Services/LogSystem/EventUndoMode.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\LogSystem; use InvalidArgumentException; diff --git a/src/Services/LogSystem/LogDataFormatter.php b/src/Services/LogSystem/LogDataFormatter.php index c5e82a47..af54c60c 100644 --- a/src/Services/LogSystem/LogDataFormatter.php +++ b/src/Services/LogSystem/LogDataFormatter.php @@ -136,7 +136,7 @@ class LogDataFormatter } try { - $dateTime = new \DateTime($date, new \DateTimeZone($timezone)); + $dateTime = new \DateTimeImmutable($date, new \DateTimeZone($timezone)); } catch (\Exception) { return 'unknown DateTime format'; } diff --git a/src/Services/LogSystem/LogEntryExtraFormatter.php b/src/Services/LogSystem/LogEntryExtraFormatter.php index fdfd72c8..ae2a5eba 100644 --- a/src/Services/LogSystem/LogEntryExtraFormatter.php +++ b/src/Services/LogSystem/LogEntryExtraFormatter.php @@ -135,7 +135,7 @@ class LogEntryExtraFormatter } if ($context instanceof LogWithCommentInterface && $context->hasComment()) { - $array[] = htmlspecialchars($context->getComment()); + $array[] = htmlspecialchars((string) $context->getComment()); } if ($context instanceof ElementCreatedLogEntry && $context->hasCreationInstockValue()) { @@ -193,7 +193,7 @@ class LogEntryExtraFormatter htmlspecialchars($this->elementTypeNameGenerator->getLocalizedTypeLabel(PartLot::class)) .' ' . $context->getMoveToTargetID(); } - if ($context->getActionTimestamp()) { + if ($context->getActionTimestamp() !== null) { $formatter = new \IntlDateFormatter($this->translator->getLocale(), \IntlDateFormatter::SHORT, \IntlDateFormatter::SHORT); $array['log.part_stock_changed.timestamp'] = $formatter->format($context->getActionTimestamp()); } diff --git a/src/Services/LogSystem/TimeTravel.php b/src/Services/LogSystem/TimeTravel.php index 1ec742e3..68d962bb 100644 --- a/src/Services/LogSystem/TimeTravel.php +++ b/src/Services/LogSystem/TimeTravel.php @@ -34,12 +34,14 @@ use App\Repository\LogEntryRepository; use Brick\Math\BigDecimal; use DateTime; use Doctrine\Common\Collections\Collection; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\MappingException; use Exception; use InvalidArgumentException; use ReflectionClass; +use Symfony\Component\PropertyAccess\PropertyAccessor; class TimeTravel { @@ -54,8 +56,11 @@ class TimeTravel /** * Undeletes the element with the given ID. * + * @template T of AbstractDBElement * @param string $class The class name of the element that should be undeleted + * @phpstan-param class-string $class * @param int $id the ID of the element that should be undeleted + * @phpstan-return T */ public function undeleteEntity(string $class, int $id): AbstractDBElement { @@ -171,17 +176,26 @@ class TimeTravel /** * This function decodes the array which is created during the json_encode of a datetime object and returns a DateTime object. * @param array $input - * @return DateTime + * @return \DateTimeInterface * @throws Exception */ - private function dateTimeDecode(?array $input): ?\DateTime + private function dateTimeDecode(?array $input, string $doctrineType): ?\DateTimeInterface { //Allow null values if ($input === null) { return null; } - return new \DateTime($input['date'], new \DateTimeZone($input['timezone'])); + //Mutable types + if (in_array($doctrineType, [Types::DATETIME_MUTABLE, Types::DATE_MUTABLE], true)) { + return new \DateTime($input['date'], new \DateTimeZone($input['timezone'])); + } + //Immutable types + if (in_array($doctrineType, [Types::DATETIME_IMMUTABLE, Types::DATE_IMMUTABLE], true)) { + return new \DateTimeImmutable($input['date'], new \DateTimeZone($input['timezone'])); + } + + throw new InvalidArgumentException('The given doctrine type is not a datetime type!'); } /** @@ -202,14 +216,24 @@ class TimeTravel $old_data = $logEntry->getOldData(); foreach ($old_data as $field => $data) { - if ($metadata->hasField($field)) { + + //We use the fieldMappings property directly instead of the hasField method, as we do not want to match the embedded field itself + //The sub fields are handled in the setField method + if (isset($metadata->fieldMappings[$field])) { //We need to convert the string to a BigDecimal first - if (!$data instanceof BigDecimal && ('big_decimal' === $metadata->getFieldMapping($field)['type'])) { + if (!$data instanceof BigDecimal && ('big_decimal' === $metadata->getFieldMapping($field)->type)) { $data = BigDecimal::of($data); } - if (!$data instanceof DateTime && ('datetime' === $metadata->getFieldMapping($field)['type'])) { - $data = $this->dateTimeDecode($data); + if (!$data instanceof \DateTimeInterface + && (in_array($metadata->getFieldMapping($field)->type, + [ + Types::DATETIME_IMMUTABLE, + Types::DATETIME_IMMUTABLE, + Types::DATE_MUTABLE, + Types::DATETIME_IMMUTABLE + ], true))) { + $data = $this->dateTimeDecode($data, $metadata->getFieldMapping($field)->type); } $this->setField($element, $field, $data); @@ -241,8 +265,22 @@ class TimeTravel */ protected function setField(AbstractDBElement $element, string $field, mixed $new_value): void { - $reflection = new ReflectionClass($element::class); - $property = $reflection->getProperty($field); + //If the field name contains a dot, it is a embeddedable object and we need to split the field name + if (str_contains($field, '.')) { + [$embedded, $embedded_field] = explode('.', $field); + + $elementClass = new ReflectionClass($element::class); + $property = $elementClass->getProperty($embedded); + $embeddedClass = $property->getValue($element); + + $embeddedReflection = new ReflectionClass($embeddedClass::class); + $property = $embeddedReflection->getProperty($embedded_field); + $target_element = $embeddedClass; + } else { + $reflection = new ReflectionClass($element::class); + $property = $reflection->getProperty($field); + $target_element = $element; + } //Check if the property is an BackedEnum, then convert the int or float value to an enum instance if ((is_string($new_value) || is_int($new_value)) @@ -253,6 +291,6 @@ class TimeTravel $new_value = $enum_class::from($new_value); } - $property->setValue($element, $new_value); + $property->setValue($target_element, $new_value); } } diff --git a/src/Services/OAuth/OAuthTokenManager.php b/src/Services/OAuth/OAuthTokenManager.php index dddde20a..9c22503b 100644 --- a/src/Services/OAuth/OAuthTokenManager.php +++ b/src/Services/OAuth/OAuthTokenManager.php @@ -47,7 +47,7 @@ final class OAuthTokenManager $tokenEntity = $this->entityManager->getRepository(OAuthToken::class)->findOneBy(['name' => $app_name]); //If the token was already existing, we just replace it with the new one - if ($tokenEntity) { + if ($tokenEntity !== null) { $tokenEntity->replaceWithNewToken($token); $this->entityManager->flush(); @@ -96,7 +96,7 @@ final class OAuthTokenManager { $token = $this->getToken($app_name); - if (!$token) { + if ($token === null) { throw new \RuntimeException('No token was saved yet for '.$app_name); } @@ -128,7 +128,7 @@ final class OAuthTokenManager $token = $this->getToken($app_name); //If the token is not existing, we return null - if (!$token) { + if ($token === null) { return null; } diff --git a/src/Services/System/BannerHelper.php b/src/Services/System/BannerHelper.php index c0dbf600..1b6da52a 100644 --- a/src/Services/System/BannerHelper.php +++ b/src/Services/System/BannerHelper.php @@ -43,7 +43,7 @@ class BannerHelper if (!is_string($banner)) { throw new \RuntimeException('The parameter "partdb.banner" must be a string.'); } - if (empty($banner)) { + if ($banner === '') { $banner_path = $this->project_dir .DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'banner.md'; diff --git a/src/Services/Trees/SidebarTreeUpdater.php b/src/Services/Trees/SidebarTreeUpdater.php index 396410db..c0f93b1f 100644 --- a/src/Services/Trees/SidebarTreeUpdater.php +++ b/src/Services/Trees/SidebarTreeUpdater.php @@ -49,7 +49,7 @@ final class SidebarTreeUpdater //This tag and therfore this whole cache gets cleared by TreeCacheInvalidationListener when a structural element is changed $item->tag('sidebar_tree_update'); - return new \DateTime(); + return new \DateTimeImmutable(); }); } } diff --git a/src/Services/UserSystem/PasswordResetManager.php b/src/Services/UserSystem/PasswordResetManager.php index 31dbdf90..1a78cb60 100644 --- a/src/Services/UserSystem/PasswordResetManager.php +++ b/src/Services/UserSystem/PasswordResetManager.php @@ -59,8 +59,7 @@ class PasswordResetManager $user->setPwResetToken($this->passwordEncoder->hash($unencrypted_token)); //Determine the expiration datetime of - $expiration_date = new DateTime(); - $expiration_date->add(date_interval_create_from_date_string('1 day')); + $expiration_date = new \DateTimeImmutable("+1 day"); $user->setPwResetExpires($expiration_date); if ($user->getEmail() !== null && $user->getEmail() !== '') { @@ -105,7 +104,7 @@ class PasswordResetManager } //Check if token is expired yet - if ($user->getPwResetExpires() < new DateTime()) { + if ($user->getPwResetExpires() < new \DateTimeImmutable()) { return false; } @@ -119,7 +118,7 @@ class PasswordResetManager //Remove token $user->setPwResetToken(null); - $user->setPwResetExpires(new DateTime()); + $user->setPwResetExpires(new \DateTimeImmutable()); //Save to DB $this->em->flush(); diff --git a/src/Services/UserSystem/TFA/DecoratedGoogleAuthenticator.php b/src/Services/UserSystem/TFA/DecoratedGoogleAuthenticator.php index 8ec411f1..05e5ed4c 100644 --- a/src/Services/UserSystem/TFA/DecoratedGoogleAuthenticator.php +++ b/src/Services/UserSystem/TFA/DecoratedGoogleAuthenticator.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\UserSystem\TFA; use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface; @@ -67,4 +69,4 @@ class DecoratedGoogleAuthenticator implements GoogleAuthenticatorInterface { return $this->inner->generateSecret(); } -} \ No newline at end of file +} diff --git a/src/Services/UserSystem/VoterHelper.php b/src/Services/UserSystem/VoterHelper.php index c13cf22a..644351f4 100644 --- a/src/Services/UserSystem/VoterHelper.php +++ b/src/Services/UserSystem/VoterHelper.php @@ -29,6 +29,9 @@ use App\Security\ApiTokenAuthenticatedToken; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +/** + * @see \App\Tests\Services\UserSystem\VoterHelperTest + */ final class VoterHelper { private readonly UserRepository $userRepository; diff --git a/src/State/PartDBInfoProvider.php b/src/State/PartDBInfoProvider.php index 7d09d721..c6760ede 100644 --- a/src/State/PartDBInfoProvider.php +++ b/src/State/PartDBInfoProvider.php @@ -1,5 +1,7 @@ . + */ + +declare(strict_types=1); + + +namespace App\Translation\Fixes; + +use Symfony\Component\DependencyInjection\Attribute\AsDecorator; +use Symfony\Component\Translation\Dumper\FileDumper; +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Exception\InvalidArgumentException; + +/** + * Backport of the XliffFile dumper from Symfony 7.2, which supports segment attributes and notes, this keeps the + * metadata when editing the translations from inside Symfony. + */ +#[AsDecorator("translation.dumper.xliff")] +class SegmentAwareXliffFileDumper extends FileDumper +{ + + public function __construct( + private string $extension = 'xlf', + ) { + } + + public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string + { + $xliffVersion = '1.2'; + if (\array_key_exists('xliff_version', $options)) { + $xliffVersion = $options['xliff_version']; + } + + if (\array_key_exists('default_locale', $options)) { + $defaultLocale = $options['default_locale']; + } else { + $defaultLocale = \Locale::getDefault(); + } + + if ('1.2' === $xliffVersion) { + return $this->dumpXliff1($defaultLocale, $messages, $domain, $options); + } + if ('2.0' === $xliffVersion) { + return $this->dumpXliff2($defaultLocale, $messages, $domain); + } + + throw new InvalidArgumentException(\sprintf('No support implemented for dumping XLIFF version "%s".', $xliffVersion)); + } + + protected function getExtension(): string + { + return $this->extension; + } + + private function dumpXliff1(string $defaultLocale, MessageCatalogue $messages, ?string $domain, array $options = []): string + { + $toolInfo = ['tool-id' => 'symfony', 'tool-name' => 'Symfony']; + if (\array_key_exists('tool_info', $options)) { + $toolInfo = array_merge($toolInfo, $options['tool_info']); + } + + $dom = new \DOMDocument('1.0', 'utf-8'); + $dom->formatOutput = true; + + $xliff = $dom->appendChild($dom->createElement('xliff')); + $xliff->setAttribute('version', '1.2'); + $xliff->setAttribute('xmlns', 'urn:oasis:names:tc:xliff:document:1.2'); + + $xliffFile = $xliff->appendChild($dom->createElement('file')); + $xliffFile->setAttribute('source-language', str_replace('_', '-', $defaultLocale)); + $xliffFile->setAttribute('target-language', str_replace('_', '-', $messages->getLocale())); + $xliffFile->setAttribute('datatype', 'plaintext'); + $xliffFile->setAttribute('original', 'file.ext'); + + $xliffHead = $xliffFile->appendChild($dom->createElement('header')); + $xliffTool = $xliffHead->appendChild($dom->createElement('tool')); + foreach ($toolInfo as $id => $value) { + $xliffTool->setAttribute($id, $value); + } + + if ($catalogueMetadata = $messages->getCatalogueMetadata('', $domain) ?? []) { + $xliffPropGroup = $xliffHead->appendChild($dom->createElement('prop-group')); + foreach ($catalogueMetadata as $key => $value) { + $xliffProp = $xliffPropGroup->appendChild($dom->createElement('prop')); + $xliffProp->setAttribute('prop-type', $key); + $xliffProp->appendChild($dom->createTextNode($value)); + } + } + + $xliffBody = $xliffFile->appendChild($dom->createElement('body')); + foreach ($messages->all($domain) as $source => $target) { + $translation = $dom->createElement('trans-unit'); + + $translation->setAttribute('id', strtr(substr(base64_encode(hash('xxh128', $source, true)), 0, 7), '/+', '._')); + $translation->setAttribute('resname', $source); + + $s = $translation->appendChild($dom->createElement('source')); + $s->appendChild($dom->createTextNode($source)); + + // Does the target contain characters requiring a CDATA section? + $text = 1 === preg_match('/[&<>]/', $target) ? $dom->createCDATASection($target) : $dom->createTextNode($target); + + $targetElement = $dom->createElement('target'); + $metadata = $messages->getMetadata($source, $domain); + if ($this->hasMetadataArrayInfo('target-attributes', $metadata)) { + foreach ($metadata['target-attributes'] as $name => $value) { + $targetElement->setAttribute($name, $value); + } + } + $t = $translation->appendChild($targetElement); + $t->appendChild($text); + + if ($this->hasMetadataArrayInfo('notes', $metadata)) { + foreach ($metadata['notes'] as $note) { + if (!isset($note['content'])) { + continue; + } + + $n = $translation->appendChild($dom->createElement('note')); + $n->appendChild($dom->createTextNode($note['content'])); + + if (isset($note['priority'])) { + $n->setAttribute('priority', $note['priority']); + } + + if (isset($note['from'])) { + $n->setAttribute('from', $note['from']); + } + } + } + + $xliffBody->appendChild($translation); + } + + return $dom->saveXML(); + } + + private function dumpXliff2(string $defaultLocale, MessageCatalogue $messages, ?string $domain): string + { + $dom = new \DOMDocument('1.0', 'utf-8'); + $dom->formatOutput = true; + + $xliff = $dom->appendChild($dom->createElement('xliff')); + $xliff->setAttribute('xmlns', 'urn:oasis:names:tc:xliff:document:2.0'); + $xliff->setAttribute('version', '2.0'); + $xliff->setAttribute('srcLang', str_replace('_', '-', $defaultLocale)); + $xliff->setAttribute('trgLang', str_replace('_', '-', $messages->getLocale())); + + $xliffFile = $xliff->appendChild($dom->createElement('file')); + if (str_ends_with($domain, MessageCatalogue::INTL_DOMAIN_SUFFIX)) { + $xliffFile->setAttribute('id', substr($domain, 0, -\strlen(MessageCatalogue::INTL_DOMAIN_SUFFIX)).'.'.$messages->getLocale()); + } else { + $xliffFile->setAttribute('id', $domain.'.'.$messages->getLocale()); + } + + if ($catalogueMetadata = $messages->getCatalogueMetadata('', $domain) ?? []) { + $xliff->setAttribute('xmlns:m', 'urn:oasis:names:tc:xliff:metadata:2.0'); + $xliffMetadata = $xliffFile->appendChild($dom->createElement('m:metadata')); + foreach ($catalogueMetadata as $key => $value) { + $xliffMeta = $xliffMetadata->appendChild($dom->createElement('prop')); + $xliffMeta->setAttribute('type', $key); + $xliffMeta->appendChild($dom->createTextNode($value)); + } + } + + foreach ($messages->all($domain) as $source => $target) { + $translation = $dom->createElement('unit'); + $translation->setAttribute('id', strtr(substr(base64_encode(hash('xxh128', $source, true)), 0, 7), '/+', '._')); + + if (\strlen($source) <= 80) { + $translation->setAttribute('name', $source); + } + + $metadata = $messages->getMetadata($source, $domain); + + // Add notes section + if ($this->hasMetadataArrayInfo('notes', $metadata)) { + $notesElement = $dom->createElement('notes'); + foreach ($metadata['notes'] as $note) { + $n = $dom->createElement('note'); + $n->appendChild($dom->createTextNode($note['content'] ?? '')); + unset($note['content']); + + foreach ($note as $name => $value) { + $n->setAttribute($name, $value); + } + $notesElement->appendChild($n); + } + $translation->appendChild($notesElement); + } + + $segment = $translation->appendChild($dom->createElement('segment')); + + if ($this->hasMetadataArrayInfo('segment-attributes', $metadata)) { + foreach ($metadata['segment-attributes'] as $name => $value) { + $segment->setAttribute($name, $value); + } + } + + $s = $segment->appendChild($dom->createElement('source')); + $s->appendChild($dom->createTextNode($source)); + + // Does the target contain characters requiring a CDATA section? + $text = 1 === preg_match('/[&<>]/', $target) ? $dom->createCDATASection($target) : $dom->createTextNode($target); + + $targetElement = $dom->createElement('target'); + if ($this->hasMetadataArrayInfo('target-attributes', $metadata)) { + foreach ($metadata['target-attributes'] as $name => $value) { + $targetElement->setAttribute($name, $value); + } + } + $t = $segment->appendChild($targetElement); + $t->appendChild($text); + + $xliffFile->appendChild($translation); + } + + return $dom->saveXML(); + } + + private function hasMetadataArrayInfo(string $key, ?array $metadata = null): bool + { + return is_iterable($metadata[$key] ?? null); + } +} \ No newline at end of file diff --git a/src/Translation/Fixes/SegmentAwareXliffFileLoader.php b/src/Translation/Fixes/SegmentAwareXliffFileLoader.php new file mode 100644 index 00000000..12455e87 --- /dev/null +++ b/src/Translation/Fixes/SegmentAwareXliffFileLoader.php @@ -0,0 +1,262 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Translation\Fixes; + +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Util\Exception\InvalidXmlException; +use Symfony\Component\Config\Util\Exception\XmlParsingException; +use Symfony\Component\Config\Util\XmlUtils; +use Symfony\Component\DependencyInjection\Attribute\AsDecorator; +use Symfony\Component\Translation\Exception\InvalidResourceException; +use Symfony\Component\Translation\Exception\NotFoundResourceException; +use Symfony\Component\Translation\Exception\RuntimeException; +use Symfony\Component\Translation\Loader\LoaderInterface; +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Util\XliffUtils; + +/** + * Backport of the XliffFile dumper from Symfony 7.2, which supports segment attributes and notes, this keeps the + * metadata when editing the translations from inside Symfony. + */ +#[AsDecorator("translation.loader.xliff")] +class SegmentAwareXliffFileLoader implements LoaderInterface +{ + public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue + { + if (!class_exists(XmlUtils::class)) { + throw new RuntimeException('Loading translations from the Xliff format requires the Symfony Config component.'); + } + + if (!$this->isXmlString($resource)) { + if (!stream_is_local($resource)) { + throw new InvalidResourceException(\sprintf('This is not a local file "%s".', $resource)); + } + + if (!file_exists($resource)) { + throw new NotFoundResourceException(\sprintf('File "%s" not found.', $resource)); + } + + if (!is_file($resource)) { + throw new InvalidResourceException(\sprintf('This is neither a file nor an XLIFF string "%s".', $resource)); + } + } + + try { + if ($this->isXmlString($resource)) { + $dom = XmlUtils::parse($resource); + } else { + $dom = XmlUtils::loadFile($resource); + } + } catch (\InvalidArgumentException|XmlParsingException|InvalidXmlException $e) { + throw new InvalidResourceException(\sprintf('Unable to load "%s": ', $resource).$e->getMessage(), $e->getCode(), $e); + } + + if ($errors = XliffUtils::validateSchema($dom)) { + throw new InvalidResourceException(\sprintf('Invalid resource provided: "%s"; Errors: ', $resource).XliffUtils::getErrorsAsString($errors)); + } + + $catalogue = new MessageCatalogue($locale); + $this->extract($dom, $catalogue, $domain); + + if (is_file($resource) && class_exists(FileResource::class)) { + $catalogue->addResource(new FileResource($resource)); + } + + return $catalogue; + } + + private function extract(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain): void + { + $xliffVersion = XliffUtils::getVersionNumber($dom); + + if ('1.2' === $xliffVersion) { + $this->extractXliff1($dom, $catalogue, $domain); + } + + if ('2.0' === $xliffVersion) { + $this->extractXliff2($dom, $catalogue, $domain); + } + } + + /** + * Extract messages and metadata from DOMDocument into a MessageCatalogue. + */ + private function extractXliff1(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain): void + { + $xml = simplexml_import_dom($dom); + $encoding = $dom->encoding ? strtoupper($dom->encoding) : null; + + $namespace = 'urn:oasis:names:tc:xliff:document:1.2'; + $xml->registerXPathNamespace('xliff', $namespace); + + foreach ($xml->xpath('//xliff:file') as $file) { + $fileAttributes = $file->attributes(); + + $file->registerXPathNamespace('xliff', $namespace); + + foreach ($file->xpath('.//xliff:prop') as $prop) { + $catalogue->setCatalogueMetadata($prop->attributes()['prop-type'], (string) $prop, $domain); + } + + foreach ($file->xpath('.//xliff:trans-unit') as $translation) { + $attributes = $translation->attributes(); + + if (!(isset($attributes['resname']) || isset($translation->source))) { + continue; + } + + $source = (string) (isset($attributes['resname']) && $attributes['resname'] ? $attributes['resname'] : $translation->source); + + if (isset($translation->target) + && 'needs-translation' === (string) $translation->target->attributes()['state'] + && \in_array((string) $translation->target, [$source, (string) $translation->source], true) + ) { + continue; + } + + // If the xlf file has another encoding specified, try to convert it because + // simple_xml will always return utf-8 encoded values + $target = $this->utf8ToCharset((string) ($translation->target ?? $translation->source), $encoding); + + $catalogue->set($source, $target, $domain); + + $metadata = [ + 'source' => (string) $translation->source, + 'file' => [ + 'original' => (string) $fileAttributes['original'], + ], + ]; + if ($notes = $this->parseNotesMetadata($translation->note, $encoding)) { + $metadata['notes'] = $notes; + } + + if (isset($translation->target) && $translation->target->attributes()) { + $metadata['target-attributes'] = []; + foreach ($translation->target->attributes() as $key => $value) { + $metadata['target-attributes'][$key] = (string) $value; + } + } + + if (isset($attributes['id'])) { + $metadata['id'] = (string) $attributes['id']; + } + + $catalogue->setMetadata($source, $metadata, $domain); + } + } + } + + private function extractXliff2(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain): void + { + $xml = simplexml_import_dom($dom); + $encoding = $dom->encoding ? strtoupper($dom->encoding) : null; + + $xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:2.0'); + + foreach ($xml->xpath('//xliff:unit') as $unit) { + foreach ($unit->segment as $segment) { + $attributes = $unit->attributes(); + $source = $attributes['name'] ?? $segment->source; + + // If the xlf file has another encoding specified, try to convert it because + // simple_xml will always return utf-8 encoded values + $target = $this->utf8ToCharset((string) ($segment->target ?? $segment->source), $encoding); + + $catalogue->set((string) $source, $target, $domain); + + $metadata = []; + if ($segment->attributes()) { + $metadata['segment-attributes'] = []; + foreach ($segment->attributes() as $key => $value) { + $metadata['segment-attributes'][$key] = (string) $value; + } + } + + if (isset($segment->target) && $segment->target->attributes()) { + $metadata['target-attributes'] = []; + foreach ($segment->target->attributes() as $key => $value) { + $metadata['target-attributes'][$key] = (string) $value; + } + } + + if (isset($unit->notes)) { + $metadata['notes'] = []; + foreach ($unit->notes->note as $noteNode) { + $note = []; + foreach ($noteNode->attributes() as $key => $value) { + $note[$key] = (string) $value; + } + $note['content'] = (string) $noteNode; + $metadata['notes'][] = $note; + } + } + + $catalogue->setMetadata((string) $source, $metadata, $domain); + } + } + } + + /** + * Convert a UTF8 string to the specified encoding. + */ + private function utf8ToCharset(string $content, ?string $encoding = null): string + { + if ('UTF-8' !== $encoding && $encoding) { + return mb_convert_encoding($content, $encoding, 'UTF-8'); + } + + return $content; + } + + private function parseNotesMetadata(?\SimpleXMLElement $noteElement = null, ?string $encoding = null): array + { + $notes = []; + + if (null === $noteElement) { + return $notes; + } + + /** @var \SimpleXMLElement $xmlNote */ + foreach ($noteElement as $xmlNote) { + $noteAttributes = $xmlNote->attributes(); + $note = ['content' => $this->utf8ToCharset((string) $xmlNote, $encoding)]; + if (isset($noteAttributes['priority'])) { + $note['priority'] = (int) $noteAttributes['priority']; + } + + if (isset($noteAttributes['from'])) { + $note['from'] = (string) $noteAttributes['from']; + } + + $notes[] = $note; + } + + return $notes; + } + + private function isXmlString(string $resource): bool + { + return str_starts_with($resource, 'providerRegistry->getProviderByKey($key); - } catch (\InvalidArgumentException $exception) { + } catch (\InvalidArgumentException) { return null; } } @@ -65,7 +65,7 @@ class InfoProviderExtension extends AbstractExtension { try { return $this->providerRegistry->getProviderByKey($key)->getProviderInfo()['name']; - } catch (\InvalidArgumentException $exception) { + } catch (\InvalidArgumentException) { return null; } } diff --git a/src/Twig/MiscExtension.php b/src/Twig/MiscExtension.php index 1edef7a3..93762d35 100644 --- a/src/Twig/MiscExtension.php +++ b/src/Twig/MiscExtension.php @@ -22,6 +22,7 @@ declare(strict_types=1); */ namespace App\Twig; +use Symfony\Component\HttpFoundation\Request; use Twig\TwigFunction; use App\Services\LogSystem\EventCommentNeededHelper; use Twig\Extension\AbstractExtension; @@ -38,6 +39,22 @@ final class MiscExtension extends AbstractExtension new TwigFunction('event_comment_needed', fn(string $operation_type) => $this->eventCommentNeededHelper->isCommentNeeded($operation_type) ), + + new TwigFunction('uri_without_host', $this->uri_without_host(...)) ]; } + + /** + * Similar to the getUri function of the request, but does not contain protocol and host. + * @param Request $request + * @return string + */ + public function uri_without_host(Request $request): string + { + if (null !== $qs = $request->getQueryString()) { + $qs = '?'.$qs; + } + + return $request->getBaseUrl().$request->getPathInfo().$qs; + } } diff --git a/src/Twig/TwigCoreExtension.php b/src/Twig/TwigCoreExtension.php index 94b067d5..352e09d3 100644 --- a/src/Twig/TwigCoreExtension.php +++ b/src/Twig/TwigCoreExtension.php @@ -42,7 +42,7 @@ final class TwigCoreExtension extends AbstractExtension { return [ /* Returns the enum cases as values */ - new TwigFunction('enum_cases', [$this, 'getEnumCases']), + new TwigFunction('enum_cases', $this->getEnumCases(...)), ]; } diff --git a/src/Twig/UserExtension.php b/src/Twig/UserExtension.php index 93ea57be..5045257a 100644 --- a/src/Twig/UserExtension.php +++ b/src/Twig/UserExtension.php @@ -127,7 +127,7 @@ final class UserExtension extends AbstractExtension public function removeLocaleFromPath(string $path): string { //Ensure the path has the correct format - if (!preg_match('/^\/\w{2}\//', $path)) { + if (!preg_match('/^\/\w{2}(?:_\w{2})?\//', $path)) { throw new \InvalidArgumentException('The given path is not a localized path!'); } diff --git a/src/Validator/Constraints/NoneOfItsChildrenValidator.php b/src/Validator/Constraints/NoneOfItsChildrenValidator.php index 2220e664..2be5f16b 100644 --- a/src/Validator/Constraints/NoneOfItsChildrenValidator.php +++ b/src/Validator/Constraints/NoneOfItsChildrenValidator.php @@ -30,6 +30,7 @@ use Symfony\Component\Validator\Exception\UnexpectedValueException; /** * The validator for the NoneOfItsChildren annotation. + * @see \App\Tests\Validator\Constraints\NoneOfItsChildrenValidatorTest */ class NoneOfItsChildrenValidator extends ConstraintValidator { diff --git a/src/Validator/Constraints/SelectableValidator.php b/src/Validator/Constraints/SelectableValidator.php index dfe8eba8..013a3964 100644 --- a/src/Validator/Constraints/SelectableValidator.php +++ b/src/Validator/Constraints/SelectableValidator.php @@ -30,6 +30,7 @@ use Symfony\Component\Validator\Exception\UnexpectedValueException; /** * The validator for the Selectable constraint. + * @see \App\Tests\Validator\Constraints\SelectableValidatorTest */ class SelectableValidator extends ConstraintValidator { diff --git a/src/Validator/Constraints/UniqueObjectCollection.php b/src/Validator/Constraints/UniqueObjectCollection.php index d432892c..c71fcc5d 100644 --- a/src/Validator/Constraints/UniqueObjectCollection.php +++ b/src/Validator/Constraints/UniqueObjectCollection.php @@ -1,4 +1,7 @@ . */ - namespace App\Validator\Constraints; use InvalidArgumentException; @@ -59,4 +61,4 @@ class UniqueObjectCollection extends Constraint throw new InvalidArgumentException(sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer))); } } -} \ No newline at end of file +} diff --git a/src/Validator/Constraints/UniqueObjectCollectionValidator.php b/src/Validator/Constraints/UniqueObjectCollectionValidator.php index 244b6c9c..b80889a4 100644 --- a/src/Validator/Constraints/UniqueObjectCollectionValidator.php +++ b/src/Validator/Constraints/UniqueObjectCollectionValidator.php @@ -1,4 +1,7 @@ . */ - namespace App\Validator\Constraints; use App\Validator\UniqueValidatableInterface; @@ -26,6 +28,9 @@ use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\UnexpectedTypeException; use Symfony\Component\Validator\Exception\UnexpectedValueException; +/** + * @see \App\Tests\Validator\Constraints\UniqueObjectCollectionValidatorTest + */ class UniqueObjectCollectionValidator extends ConstraintValidator { @@ -106,4 +111,4 @@ class UniqueObjectCollectionValidator extends ConstraintValidator return $output; } -} \ No newline at end of file +} diff --git a/src/Validator/Constraints/UrlOrBuiltinValidator.php b/src/Validator/Constraints/UrlOrBuiltinValidator.php index af498d2a..71407a6a 100644 --- a/src/Validator/Constraints/UrlOrBuiltinValidator.php +++ b/src/Validator/Constraints/UrlOrBuiltinValidator.php @@ -34,6 +34,7 @@ use function is_object; * The validator for UrlOrBuiltin. * It checks if the value is either a builtin ressource or a valid url. * In both cases it is not checked, if the ressource is really existing. + * @see \App\Tests\Validator\Constraints\UrlOrBuiltinValidatorTest */ class UrlOrBuiltinValidator extends UrlValidator { diff --git a/src/Validator/Constraints/ValidGoogleAuthCodeValidator.php b/src/Validator/Constraints/ValidGoogleAuthCodeValidator.php index f7f9e26e..25afe57b 100644 --- a/src/Validator/Constraints/ValidGoogleAuthCodeValidator.php +++ b/src/Validator/Constraints/ValidGoogleAuthCodeValidator.php @@ -33,6 +33,9 @@ use Symfony\Component\Validator\Exception\UnexpectedValueException; use function is_string; use function strlen; +/** + * @see \App\Tests\Validator\Constraints\ValidGoogleAuthCodeValidatorTest + */ class ValidGoogleAuthCodeValidator extends ConstraintValidator { public function __construct(private readonly GoogleAuthenticatorInterface $googleAuthenticator, private readonly Security $security) diff --git a/src/Validator/Constraints/ValidPermissionValidator.php b/src/Validator/Constraints/ValidPermissionValidator.php index b1a5b623..afb7721b 100644 --- a/src/Validator/Constraints/ValidPermissionValidator.php +++ b/src/Validator/Constraints/ValidPermissionValidator.php @@ -63,11 +63,11 @@ class ValidPermissionValidator extends ConstraintValidator if ($changed) { //Check if this was called in context of UserController $request = $this->requestStack->getMainRequest(); - if (!$request) { + if ($request === null) { return; } //Determine the controller class (the part before the ::) - $controller_class = explode('::', $request->attributes->get('_controller'))[0]; + $controller_class = explode('::', (string) $request->attributes->get('_controller'))[0]; if (in_array($controller_class, [UserController::class, GroupController::class], true)) { /** @var Session $session */ diff --git a/src/Validator/Constraints/ValidThemeValidator.php b/src/Validator/Constraints/ValidThemeValidator.php index 5d222934..713be9a5 100644 --- a/src/Validator/Constraints/ValidThemeValidator.php +++ b/src/Validator/Constraints/ValidThemeValidator.php @@ -26,6 +26,9 @@ use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\UnexpectedTypeException; +/** + * @see \App\Tests\Validator\Constraints\ValidThemeValidatorTest + */ class ValidThemeValidator extends ConstraintValidator { public function __construct(private readonly array $available_themes) diff --git a/src/Validator/UniqueValidatableInterface.php b/src/Validator/UniqueValidatableInterface.php index 97e3a0b9..3d954490 100644 --- a/src/Validator/UniqueValidatableInterface.php +++ b/src/Validator/UniqueValidatableInterface.php @@ -1,4 +1,7 @@ . */ - namespace App\Validator; interface UniqueValidatableInterface @@ -29,4 +31,4 @@ interface UniqueValidatableInterface * @return array An array of the form ['field1' => 'value1', 'field2' => 'value2', ...] */ public function getComparableFields(): array; -} \ No newline at end of file +} diff --git a/symfony.lock b/symfony.lock index 25ef4331..8f230496 100644 --- a/symfony.lock +++ b/symfony.lock @@ -40,16 +40,6 @@ "./config/packages/dama_doctrine_test_bundle.yaml" ] }, - "doctrine/annotations": { - "version": "1.14", - "recipe": { - "repo": "github.com/symfony/recipes", - "branch": "main", - "version": "1.10", - "ref": "64d8583af5ea57b7afa4aba4b159907f3a148b05" - }, - "files": [] - }, "doctrine/cache": { "version": "v1.8.0" }, @@ -167,6 +157,9 @@ "jbtronics/dompdf-font-loader-bundle": { "version": "v1.1.1" }, + "jbtronics/translation-editor-bundle": { + "version": "v1.0" + }, "knpuniversity/oauth2-client-bundle": { "version": "2.15", "recipe": { @@ -237,9 +230,6 @@ "nikolaposa/version": { "version": "2.2.2" }, - "nyholm/nsa": { - "version": "1.1.0" - }, "nyholm/psr7": { "version": "1.0", "recipe": { @@ -291,30 +281,6 @@ "php-http/promise": { "version": "v1.0.0" }, - "php-translation/common": { - "version": "1.0.0" - }, - "php-translation/extractor": { - "version": "1.7.1" - }, - "php-translation/symfony-bundle": { - "version": "0.12", - "recipe": { - "repo": "github.com/symfony/recipes-contrib", - "branch": "master", - "version": "0.10", - "ref": "f3ca4e4da63897d177e58da78626c20648c0e102" - }, - "files": [ - "config/packages/dev/php_translation.yaml", - "config/packages/php_translation.yaml", - "config/routes/dev/php_translation.yaml", - "config/routes/php_translation.yaml" - ] - }, - "php-translation/symfony-storage": { - "version": "1.0.1" - }, "phpdocumentor/reflection-common": { "version": "1.0.1" }, @@ -634,9 +600,6 @@ "symfony/polyfill-mbstring": { "version": "v1.10.0" }, - "symfony/polyfill-php72": { - "version": "v1.10.0" - }, "symfony/polyfill-php80": { "version": "v1.17.0" }, diff --git a/templates/_navbar.html.twig b/templates/_navbar.html.twig index 0d775b8d..cd1f641f 100644 --- a/templates/_navbar.html.twig +++ b/templates/_navbar.html.twig @@ -3,11 +3,18 @@