diff --git a/.docker/partdb-entrypoint.sh b/.docker/partdb-entrypoint.sh index 5bfc6b483..f5071e223 100644 --- a/.docker/partdb-entrypoint.sh +++ b/.docker/partdb-entrypoint.sh @@ -40,7 +40,49 @@ if [ -d /var/www/html/var/db ]; then fi # Start PHP-FPM (the PHP_VERSION is replaced by the configured version in the Dockerfile) -service phpPHP_VERSION-fpm start +php-fpmPHP_VERSION -F & + + +# Run migrations if automigration is enabled via env variable DB_AUTOMIGRATE +if [ "$DB_AUTOMIGRATE" = "true" ]; then + echo "Waiting for database to be ready..." + ATTEMPTS_LEFT_TO_REACH_DATABASE=60 + until [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ] || DATABASE_ERROR=$(sudo -E -u www-data php bin/console dbal:run-sql -q "SELECT 1" 2>&1); do + if [ $? -eq 255 ]; then + # If the Doctrine command exits with 255, an unrecoverable error occurred + ATTEMPTS_LEFT_TO_REACH_DATABASE=0 + break + fi + sleep 1 + ATTEMPTS_LEFT_TO_REACH_DATABASE=$((ATTEMPTS_LEFT_TO_REACH_DATABASE - 1)) + echo "Still waiting for database to be ready... Or maybe the database is not reachable. $ATTEMPTS_LEFT_TO_REACH_DATABASE attempts left." + done + + if [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ]; then + echo "The database is not up or not reachable:" + echo "$DATABASE_ERROR" + exit 1 + else + echo "The database is now ready and reachable" + fi + + # Check if there are any available migrations to do, by executing doctrine:migrations:up-to-date + # and checking if the exit code is 0 (up to date) or 1 (not up to date) + if sudo -E -u www-data php bin/console doctrine:migrations:up-to-date --no-interaction; then + echo "Database is up to date, no migrations necessary." + else + echo "Migrations available..." + echo "Do backup of database..." + + sudo -E -u www-data mkdir -p /var/www/html/uploads/.automigration-backup/ + # Backup the database + sudo -E -u www-data php bin/console partdb:backup -n --database /var/www/html/uploads/.automigration-backup/backup-$(date +%Y-%m-%d_%H-%M-%S).zip + + # Check if there are any migration files + sudo -E -u www-data php bin/console doctrine:migrations:migrate --no-interaction + fi + +fi # 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 @@ -48,4 +90,4 @@ if [ "${1#-}" != "$1" ]; then fi # Pass to the original entrypoint -exec "$@" \ No newline at end of file +exec "$@" diff --git a/.docker/symfony.conf b/.docker/symfony.conf index 5788a6301..aa88eef2c 100644 --- a/.docker/symfony.conf +++ b/.docker/symfony.conf @@ -24,32 +24,10 @@ ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined - # Pass the configuration from the docker env to the PHP environment (here you should list all .env options) - PassEnv APP_ENV APP_DEBUG APP_SECRET REDIRECT_TO_HTTPS DISABLE_YEAR2038_BUG_CHECK - PassEnv TRUSTED_PROXIES TRUSTED_HOSTS LOCK_DSN - PassEnv DATABASE_URL ENFORCE_CHANGE_COMMENTS_FOR DATABASE_MYSQL_USE_SSL_CA DATABASE_MYSQL_SSL_VERIFY_CERT - PassEnv DEFAULT_LANG DEFAULT_TIMEZONE BASE_CURRENCY INSTANCE_NAME ALLOW_ATTACHMENT_DOWNLOADS USE_GRAVATAR MAX_ATTACHMENT_FILE_SIZE DEFAULT_URI CHECK_FOR_UPDATES ATTACHMENT_DOWNLOAD_BY_DEFAULT - PassEnv MAILER_DSN ALLOW_EMAIL_PW_RESET EMAIL_SENDER_EMAIL EMAIL_SENDER_NAME - PassEnv HISTORY_SAVE_CHANGED_FIELDS HISTORY_SAVE_CHANGED_DATA HISTORY_SAVE_REMOVED_DATA HISTORY_SAVE_NEW_DATA - PassEnv ERROR_PAGE_ADMIN_EMAIL ERROR_PAGE_SHOW_HELP - PassEnv DEMO_MODE NO_URL_REWRITE_AVAILABLE FIXER_API_KEY BANNER - # In old version the SAML sp private key env, was wrongly named SAMLP_SP_PRIVATE_KEY, keep it for backward compatibility - PassEnv SAML_ENABLED SAML_BEHIND_PROXY SAML_ROLE_MAPPING SAML_UPDATE_GROUP_ON_LOGIN SAML_IDP_ENTITY_ID SAML_IDP_SINGLE_SIGN_ON_SERVICE SAML_IDP_SINGLE_LOGOUT_SERVICE SAML_IDP_X509_CERT SAML_SP_ENTITY_ID SAML_SP_X509_CERT SAML_SP_PRIVATE_KEY SAMLP_SP_PRIVATE_KEY - PassEnv TABLE_DEFAULT_PAGE_SIZE TABLE_PARTS_DEFAULT_COLUMNS - - PassEnv PROVIDER_DIGIKEY_CLIENT_ID PROVIDER_DIGIKEY_SECRET PROVIDER_DIGIKEY_CURRENCY PROVIDER_DIGIKEY_LANGUAGE PROVIDER_DIGIKEY_COUNTRY - PassEnv PROVIDER_ELEMENT14_KEY PROVIDER_ELEMENT14_STORE_ID - PassEnv PROVIDER_TME_KEY PROVIDER_TME_SECRET PROVIDER_TME_CURRENCY PROVIDER_TME_LANGUAGE PROVIDER_TME_COUNTRY PROVIDER_TME_GET_GROSS_PRICES - 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 # enabled or disabled at a global level, it is possible to # include a line for only one particular virtual host. For example the # following line enables the CGI configuration for this host only # after it has been globally disabled with "a2disconf". #Include conf-available/serve-cgi-bin.conf - \ No newline at end of file + diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..66990769e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# editorconfig.org + +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[{compose.yaml,compose.*.yaml}] +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/.env b/.env index ce2a6b03f..982d4bbd0 100644 --- a/.env +++ b/.env @@ -31,37 +31,10 @@ DATABASE_EMULATE_NATURAL_SORT=0 # General settings ################################################################################### -# The language to use serverwide as default (en, de, ru, etc.) -DEFAULT_LANG="en" -# The default timezone to use serverwide (e.g. Europe/Berlin) -DEFAULT_TIMEZONE="Europe/Berlin" -# 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 -BASE_CURRENCY="EUR" -# The name of this installation. This will be shown as title in the browser and in the header of the website -INSTANCE_NAME="Part-DB" -# Allow users to download attachments to the server by providing an URL -# This could be a potential security issue, as the user can retrieve any file the server has access to (via internet) -ALLOW_ATTACHMENT_DOWNLOADS=0 -# Set this to 1, if the "download external files" checkbox should be checked by default for new attachments -ATTACHMENT_DOWNLOAD_BY_DEFAULT=0 -# Use gravatars for user avatars, when user has no own avatar defined -USE_GRAVATAR=0 -# The maximum allowed size for attachment files in bytes (you can use M for megabytes and G for gigabytes) -# Please note that the php.ini setting upload_max_filesize also limits the maximum size of uploaded files -MAX_ATTACHMENT_FILE_SIZE="100M" - # The public reachable URL of this Part-DB installation. This is used for generating links in SAML and email templates # This must end with a slash! DEFAULT_URI="https://partdb.changeme.invalid/" -# With this option you can configure, where users are enforced to give a change reason, which will be logged -# This is a comma separated list of values, see documentation for available values -# Leave this empty, to make all change reasons optional -ENFORCE_CHANGE_COMMENTS_FOR="" - -# Disable that if you do not want that Part-DB connects to GitHub to check for available updates, or if your server can not connect to the internet -CHECK_FOR_UPDATES=1 - ################################################################################### # Email settings ################################################################################### @@ -78,21 +51,6 @@ EMAIL_SENDER_NAME="Part-DB Mailer" # Set this to 1 to allow reset of a password per email ALLOW_EMAIL_PW_RESET=0 -###################################################################################### -# History/Eventlog settings -###################################################################################### -# If you want to use full timetrave functionality all values below have to be set to 1 - -# Save which fields were changed in a ElementEdited log entry -HISTORY_SAVE_CHANGED_FIELDS=1 -# Save the old data in the ElementEdited log entry (warning this could increase the database size in short time) -HISTORY_SAVE_CHANGED_DATA=1 -# Save the data of an element that gets removed into log entry. This allows to undelete an element -HISTORY_SAVE_REMOVED_DATA=1 -# Save the new data of an element that gets changed or added. This allows an easy comparison of the old and new data on the detail page -# This option only becomes active when HISTORY_SAVE_CHANGED_DATA is set to 1 -HISTORY_SAVE_NEW_DATA=1 - ################################################################################### # Error pages settings ################################################################################### @@ -102,127 +60,6 @@ ERROR_PAGE_ADMIN_EMAIL='' # If this is set to true, solutions to common problems are shown on error pages. Disable this, if you do not want your users to see them... ERROR_PAGE_SHOW_HELP=1 -################################################################################## -# Part table settings -################################################################################## - -# The default page size for the part table (set to -1 to show all parts on one page) -TABLE_DEFAULT_PAGE_SIZE=50 -# Configure which columns will be visible by default in the parts table (and in which order). -# This is a comma separated list of column names. See documentation for available values. -TABLE_PARTS_DEFAULT_COLUMNS=name,description,category,footprint,manufacturer,storage_location,amount - -################################################################################## -# Info provider settings -################################################################################## - -# Digikey Provider: -# You can get your client id and secret from https://developer.digikey.com/ -PROVIDER_DIGIKEY_CLIENT_ID= -PROVIDER_DIGIKEY_SECRET= -# The currency to get prices in -PROVIDER_DIGIKEY_CURRENCY=EUR -# The language to get results in (en, de, fr, it, es, zh, ja, ko) -PROVIDER_DIGIKEY_LANGUAGE=en -# The country to get results for -PROVIDER_DIGIKEY_COUNTRY=DE - -# Farnell Provider: -# You can get your API key from https://partner.element14.com/ -PROVIDER_ELEMENT14_KEY= -# Configure the store domain you want to use. This decides the language and currency of results. You can get a list of available stores from https://partner.element14.com/docs/Product_Search_API_REST__Description -PROVIDER_ELEMENT14_STORE_ID=de.farnell.com - -# TME Provider: -# You can get your API key from https://developers.tme.eu/en/ -PROVIDER_TME_KEY= -PROVIDER_TME_SECRET= -# The currency to get prices in -PROVIDER_TME_CURRENCY=EUR -# The language to get results in (en, de, pl) -PROVIDER_TME_LANGUAGE=en -# The country to get results for -PROVIDER_TME_COUNTRY=DE -# Set this to 1 to get gross prices (including VAT) instead of net prices -PROVIDER_TME_GET_GROSS_PRICES=1 - -# Octopart / Nexar Provider: -# You can get your API key from https://nexar.com/api -PROVIDER_OCTOPART_CLIENT_ID= -PROVIDER_OCTOPART_SECRET= -# The currency and country to get prices for (you have to set both to get meaningful results) -# 3 letter ISO currency code (e.g. EUR, USD, GBP) -PROVIDER_OCTOPART_CURRENCY=EUR -# 2 letter ISO country code (e.g. DE, US, GB) -PROVIDER_OCTOPART_COUNTRY=DE -# The number of results to get from Octopart while searching (please note that this counts towards your API limits) -PROVIDER_OCTOPART_SEARCH_LIMIT=10 -# Set to false to include non authorized offers in the results -PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS=1 - -# Mouser Provider API V2: -# You can get your API key from https://www.mouser.it/api-hub/ -PROVIDER_MOUSER_KEY= -# Filter search results by RoHS compliance and stock availability: -# Available options: None | Rohs | InStock | RohsAndInStock -PROVIDER_MOUSER_SEARCH_OPTION='None' -# The number of results to get from Mouser while searching (please note that this value is max 50) -PROVIDER_MOUSER_SEARCH_LIMIT=50 -# It is recommended to leave this set to 'true'. The option is not really good doumented by Mouser: -# Used when searching for keywords in the language specified when you signed up for Search API. -PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE='true' - -# LCSC Provider: -# LCSC does not provide an offical API, so this used the API LCSC uses to render their webshop. -# LCSC did not intended the use of this API and it could break any time, so use it at your own risk. - -# We dont require an API key for LCSC, just set this to 1 to enable LCSC support -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 -################################################################################## - -# This value determines the depth of the category tree, that is visible inside KiCad -# 0 means that only the top level categories are visible. Set to a value > 0 to show more levels. -# Set to -1, to show all parts of Part-DB inside a single category in KiCad -EDA_KICAD_CATEGORY_DEPTH=0 ################################################################################### # SAML Single sign on-settings @@ -276,16 +113,6 @@ NO_URL_REWRITE_AVAILABLE=0 # Set to 1, if Part-DB should redirect all HTTP requests to HTTPS. You dont need to configure this, if your webserver already does this. REDIRECT_TO_HTTPS=0 -# If you want to use fixer.io for currency conversion, you have to set this to your API key -FIXER_API_KEY=CHANGEME - -# Override value if you want to show to show a given text on homepage. -# When this is empty the content of config/banner.md is used as banner -BANNER="" - -APP_ENV=prod -APP_SECRET=a03498528f5a5fc089273ec9ae5b2849 - # Set this to zero, if you want to disable the year 2038 bug check on 32-bit systems (it will cause errors with current 32-bit PHP versions) DISABLE_YEAR2038_BUG_CHECK=0 @@ -303,3 +130,8 @@ LOCK_DSN=flock ###> nelmio/cors-bundle ### CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$' ###< nelmio/cors-bundle ### + +###> symfony/framework-bundle ### +APP_ENV=prod +APP_SECRET=a03498528f5a5fc089273ec9ae5b2849 +###< symfony/framework-bundle ### diff --git a/.env.dev b/.env.dev index e69de29bb..53b058776 100644 --- a/.env.dev +++ b/.env.dev @@ -0,0 +1,4 @@ + +###> symfony/framework-bundle ### +APP_SECRET=318b5d659e07a0b3f96d9b3a83b254ca +###< symfony/framework-bundle ### diff --git a/.env.test b/.env.test index 3dbece811..9117ff163 100644 --- a/.env.test +++ b/.env.test @@ -10,4 +10,6 @@ DATABASE_URL="sqlite:///%kernel.project_dir%/var/app_test.db" #DATABASE_URL=mysql://root:@127.0.0.1:3306/part-db # Disable update checks, as tests would fail, when github is not reachable -CHECK_FOR_UPDATES=0 \ No newline at end of file +CHECK_FOR_UPDATES=0 + +INSTANCE_NAME="Part-DB" \ No newline at end of file diff --git a/.github/workflows/assets_artifact_build.yml b/.github/workflows/assets_artifact_build.yml index 0bbfe4322..447f95bfb 100644 --- a/.github/workflows/assets_artifact_build.yml +++ b/.github/workflows/assets_artifact_build.yml @@ -1,5 +1,8 @@ name: Build assets artifact +permissions: + contents: read + on: push: branches: @@ -19,7 +22,7 @@ jobs: APP_ENV: prod steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -39,7 +42,7 @@ jobs: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: | - ${{ runner.os }}-composer- + ${{ runner.os }}-composer- - name: Install dependencies run: composer install --prefer-dist --no-progress --no-dev -a @@ -57,9 +60,9 @@ jobs: ${{ runner.os }}-yarn- - name: Setup node - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: - node-version: '18' + node-version: '20' - name: Install yarn dependencies run: yarn install diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml index 64287d834..c912e7696 100644 --- a/.github/workflows/docker_build.yml +++ b/.github/workflows/docker_build.yml @@ -1,5 +1,8 @@ name: Docker Image Build +permissions: + contents: read + on: #schedule: # - cron: '0 10 * * *' # everyday at 10am @@ -17,7 +20,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Docker meta id: docker_meta @@ -73,4 +76,4 @@ jobs: tags: ${{ steps.docker_meta.outputs.tags }} labels: ${{ steps.docker_meta.outputs.labels }} cache-from: type=gha - cache-to: type=gha,mode=max \ No newline at end of file + cache-to: type=gha,mode=max diff --git a/.github/workflows/docker_frankenphp.yml b/.github/workflows/docker_frankenphp.yml index d8cd06956..0b2eb8742 100644 --- a/.github/workflows/docker_frankenphp.yml +++ b/.github/workflows/docker_frankenphp.yml @@ -1,5 +1,8 @@ name: Docker Image Build (FrankenPHP) +permissions: + contents: read + on: #schedule: # - cron: '0 10 * * *' # everyday at 10am @@ -17,7 +20,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Docker meta id: docker_meta @@ -74,4 +77,4 @@ jobs: tags: ${{ steps.docker_meta.outputs.tags }} labels: ${{ steps.docker_meta.outputs.labels }} cache-from: type=gha - cache-to: type=gha,mode=max \ No newline at end of file + cache-to: type=gha,mode=max diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 20150b282..1de98ee9c 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -1,5 +1,8 @@ name: Static analysis +permissions: + contents: read + on: push: branches: @@ -16,7 +19,7 @@ jobs: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -30,20 +33,20 @@ jobs: id: composer-cache run: | echo "::set-output name=dir::$(composer config cache-files-dir)" - + - uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: | - ${{ runner.os }}-composer- + ${{ runner.os }}-composer- - name: Install dependencies run: composer install --prefer-dist --no-progress - name: Lint config files run: ./bin/console lint:yaml config --parse-tags - + - name: Lint twig templates run: ./bin/console lint:twig templates --env=prod @@ -53,13 +56,13 @@ jobs: - name: Check dependencies for security uses: symfonycorp/security-checker-action@v5 - + - name: Check doctrine mapping run: ./bin/console doctrine:schema:validate --skip-sync -vvv --no-interaction # Use the -d option to raise the max nesting level - name: Generate dev container run: php -d xdebug.max_nesting_level=1000 ./bin/console cache:clear --env dev - + - name: Run PHPstan run: composer phpstan diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8e6ea54c5..c7c0965b4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,5 +1,8 @@ name: PHPUnit Tests +permissions: + contents: read + on: push: branches: @@ -9,7 +12,7 @@ on: branches: - '*' - "!l10n_*" - + jobs: phpunit: name: PHPUnit and coverage Test (PHP ${{ matrix.php-versions }}, ${{ matrix.db-type }}) @@ -18,7 +21,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: [ '8.1', '8.2', '8.3', '8.4' ] + php-versions: ['8.2', '8.3', '8.4', '8.5' ] db-type: [ 'mysql', 'sqlite', 'postgres' ] env: @@ -43,7 +46,7 @@ jobs: if: matrix.db-type == 'postgres' - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -52,7 +55,7 @@ jobs: coverage: pcov ini-values: xdebug.max_nesting_level=1000 extensions: mbstring, intl, gd, xsl, gmp, bcmath, :php-psr - + - name: Start MySQL run: sudo systemctl start mysql.service if: matrix.db-type == 'mysql' @@ -71,9 +74,9 @@ jobs: # mysql version: 5.7 # mysql database: 'part-db' # mysql root password: '1234' - + ## Setup caches - + - name: Get Composer Cache Directory id: composer-cache run: | @@ -83,8 +86,8 @@ jobs: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: | - ${{ runner.os }}-composer- - + ${{ runner.os }}-composer- + - name: Get yarn cache directory path id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" @@ -96,48 +99,48 @@ jobs: key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn- - + - name: Install composer dependencies run: composer install --prefer-dist --no-progress - + - name: Setup node - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: - node-version: '18' - + node-version: '20' + - name: Install yarn dependencies run: yarn install - + - name: Build frontend run: yarn build - + - name: Create DB run: php bin/console --env test doctrine:database:create --if-not-exists -n if: matrix.db-type == 'mysql' || matrix.db-type == 'postgres' - + - name: Do migrations run: php bin/console --env test doctrine:migrations:migrate -n # Use our own custom fixtures loading command to circumvent some problems with reset the autoincrement values - name: Load fixtures run: php bin/console --env test partdb:fixtures:load -n - + - name: Run PHPunit and generate coverage run: ./bin/phpunit --coverage-clover=coverage.xml - + - name: Upload coverage uses: codecov/codecov-action@v5 with: env_vars: PHP_VERSION,DB_TYPE token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true - + - name: Test app:clean-attachments run: php bin/console partdb:attachments:clean-unused -n - + - name: Test app:convert-bbcode run: php bin/console app:convert-bbcode -n - + - name: Test app:show-logs run: php bin/console app:show-logs -n diff --git a/.gitignore b/.gitignore index b726f64c6..dd5c43db5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /.env.local /.env.local.php /.env.*.local +/.env.local.bak /config/secrets/prod/prod.decrypt.private.php /public/bundles/ /var/ @@ -41,9 +42,12 @@ yarn-error.log ###> phpunit/phpunit ### /phpunit.xml -.phpunit.result.cache +/.phpunit.cache/ ###< phpunit/phpunit ### ###> phpstan/phpstan ### phpstan.neon ###< phpstan/phpstan ### + +.claude/ +CLAUDE.md \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 0f909f168..cb18c78f4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ ARG BASE_IMAGE=debian:bookworm-slim -ARG PHP_VERSION=8.3 +ARG PHP_VERSION=8.4 FROM ${BASE_IMAGE} AS base ARG PHP_VERSION @@ -48,7 +48,7 @@ RUN apt-get update && apt-get -y install \ # 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 - && \ + curl -sL https://deb.nodesource.com/setup_22.x | bash - && \ apt-get update && apt-get install -y \ nodejs \ yarn \ @@ -119,12 +119,12 @@ realpath_cache_size=4096K realpath_cache_ttl=600 EOF -# Increase upload limit and enable preloading +# Increase upload limit and enable preloading (disabled for now, as it does not seem to work properly, and require prod env anyway) COPY </dev/null; \ + chmod 644 /etc/apt/keyrings/yarn.gpg; \ + \ + # Add Yarn repo with signed-by + echo "deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian stable main" \ + | tee /etc/apt/sources.list.d/yarn.list; \ + \ + # Run NodeSource setup script (unchanged) + curl -sL https://deb.nodesource.com/setup_22.x | bash -; \ + \ + # Install Node.js + Yarn + apt-get update; \ + apt-get install -y --no-install-recommends \ + nodejs \ + yarn; \ + \ + # Cleanup + apt-get -y autoremove; \ + apt-get clean autoclean; \ + rm -rf /var/lib/apt/lists/* + # Install PHP RUN set -eux; \ diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..bc4d0bf3e --- /dev/null +++ b/Makefile @@ -0,0 +1,91 @@ +# PartDB Makefile for Test Environment Management + +.PHONY: help deps-install lint format format-check test coverage pre-commit all test-typecheck \ +test-setup test-clean test-db-create test-db-migrate test-cache-clear test-fixtures test-run test-reset \ +section-dev dev-setup dev-clean dev-db-create dev-db-migrate dev-cache-clear dev-warmup dev-reset + +# Default target +help: ## Show this help + @awk 'BEGIN {FS = ":.*##"}; /^[a-zA-Z0-9][a-zA-Z0-9_-]+:.*##/ {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) + +# Dependencies +deps-install: ## Install PHP dependencies with unlimited memory + @echo "๐Ÿ“ฆ Installing PHP dependencies..." + COMPOSER_MEMORY_LIMIT=-1 composer install + yarn install + @echo "โœ… Dependencies installed" + +# Complete test environment setup +test-setup: test-clean test-db-create test-db-migrate test-fixtures ## Complete test setup (clean, create DB, migrate, fixtures) + @echo "โœ… Test environment setup complete!" + +# Clean test environment +test-clean: ## Clean test cache and database files + @echo "๐Ÿงน Cleaning test environment..." + rm -rf var/cache/test + rm -f var/app_test.db + @echo "โœ… Test environment cleaned" + +# Create test database +test-db-create: ## Create test database (if not exists) + @echo "๐Ÿ—„๏ธ Creating test database..." + -php bin/console doctrine:database:create --if-not-exists --env test || echo "โš ๏ธ Database creation failed (expected for SQLite) - continuing..." + +# Run database migrations for test environment +test-db-migrate: ## Run database migrations for test environment + @echo "๐Ÿ”„ Running database migrations..." + COMPOSER_MEMORY_LIMIT=-1 php bin/console doctrine:migrations:migrate -n --env test + +# Clear test cache +test-cache-clear: ## Clear test cache + @echo "๐Ÿ—‘๏ธ Clearing test cache..." + rm -rf var/cache/test + @echo "โœ… Test cache cleared" + +# Load test fixtures +test-fixtures: ## Load test fixtures + @echo "๐Ÿ“ฆ Loading test fixtures..." + php bin/console partdb:fixtures:load -n --env test + +# Run PHPUnit tests +test-run: ## Run PHPUnit tests + @echo "๐Ÿงช Running tests..." + php bin/phpunit + +# Quick test reset (clean + migrate + fixtures, skip DB creation) +test-reset: test-cache-clear test-db-migrate test-fixtures + @echo "โœ… Test environment reset complete!" + +test-typecheck: ## Run static analysis (PHPStan) + @echo "๐Ÿงช Running type checks..." + COMPOSER_MEMORY_LIMIT=-1 composer phpstan + +# Development helpers +dev-setup: dev-clean dev-db-create dev-db-migrate dev-warmup ## Complete development setup (clean, create DB, migrate, warmup) + @echo "โœ… Development environment setup complete!" + +dev-clean: ## Clean development cache and database files + @echo "๐Ÿงน Cleaning development environment..." + rm -rf var/cache/dev + rm -f var/app_dev.db + @echo "โœ… Development environment cleaned" + +dev-db-create: ## Create development database (if not exists) + @echo "๐Ÿ—„๏ธ Creating development database..." + -php bin/console doctrine:database:create --if-not-exists --env dev || echo "โš ๏ธ Database creation failed (expected for SQLite) - continuing..." + +dev-db-migrate: ## Run database migrations for development environment + @echo "๐Ÿ”„ Running database migrations..." + COMPOSER_MEMORY_LIMIT=-1 php bin/console doctrine:migrations:migrate -n --env dev + +dev-cache-clear: ## Clear development cache + @echo "๐Ÿ—‘๏ธ Clearing development cache..." + rm -rf var/cache/dev + @echo "โœ… Development cache cleared" + +dev-warmup: ## Warm up development cache + @echo "๐Ÿ”ฅ Warming up development cache..." + COMPOSER_MEMORY_LIMIT=-1 php -d memory_limit=1G bin/console cache:warmup --env dev -n + +dev-reset: dev-cache-clear dev-db-migrate ## Quick development reset (cache clear + migrate) + @echo "โœ… Development environment reset complete!" \ No newline at end of file diff --git a/README.md b/README.md index 74ebfe7f6..291b574ab 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ![Static analysis](https://github.com/Part-DB/Part-DB-symfony/workflows/Static%20analysis/badge.svg) [![codecov](https://codecov.io/gh/Part-DB/Part-DB-server/branch/master/graph/badge.svg)](https://codecov.io/gh/Part-DB/Part-DB-server) ![GitHub License](https://img.shields.io/github/license/Part-DB/Part-DB-symfony) -![PHP Version](https://img.shields.io/badge/PHP-%3E%3D%208.1-green) +![PHP Version](https://img.shields.io/badge/PHP-%3E%3D%208.2-green) ![Docker Pulls](https://img.shields.io/docker/pulls/jbtronics/part-db1) ![Docker Build Status](https://github.com/Part-DB/Part-DB-symfony/workflows/Docker%20Image%20Build/badge.svg) @@ -75,10 +75,10 @@ Part-DB is also used by small companies and universities for managing their inve * A **web server** (like Apache2 or nginx) that is capable of running [Symfony 6](https://symfony.com/doc/current/reference/requirements.html), - this includes a minimum PHP version of **PHP 8.1** + this includes a minimum PHP version of **PHP 8.2** * A **MySQL** (at least 5.7) /**MariaDB** (at least 10.4) database server, or **PostgreSQL** 10+ if you do not want to use SQLite. * Shell access to your server is highly recommended! -* For building the client-side assets **yarn** and **nodejs** (>= 18.0) is needed. +* For building the client-side assets **yarn** and **nodejs** (>= 20.0) is needed. ## Installation diff --git a/VERSION b/VERSION index ace44233b..c043eea77 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.15.1 +2.2.1 diff --git a/assets/ckeditor/emojis.json b/assets/ckeditor/emojis.json new file mode 100644 index 000000000..a6bafe26c --- /dev/null +++ b/assets/ckeditor/emojis.json @@ -0,0 +1 @@ +[{"emoji":"๐Ÿ˜€","group":0,"order":1,"tags":["cheerful","cheery","face","grin","grinning","happy","laugh","nice","smile","smiling","teeth"],"version":1,"annotation":"grinning face","shortcodes":["grinning","grinning_face"]},{"emoji":"๐Ÿ˜ƒ","group":0,"order":2,"tags":["awesome","big","eyes","face","grin","grinning","happy","mouth","open","smile","smiling","teeth","yay"],"version":0.6,"annotation":"grinning face with big eyes","shortcodes":["grinning_face_with_big_eyes","smiley"]},{"emoji":"๐Ÿ˜„","group":0,"order":3,"tags":["eye","eyes","face","grin","grinning","happy","laugh","lol","mouth","open","smile","smiling"],"version":0.6,"annotation":"grinning face with smiling eyes","emoticon":":D","shortcodes":["grinning_face_with_closed_eyes","smile"]},{"emoji":"๐Ÿ˜","group":0,"order":4,"tags":["beaming","eye","eyes","face","grin","grinning","happy","nice","smile","smiling","teeth"],"version":0.6,"annotation":"beaming face with smiling eyes","shortcodes":["beaming_face","grin"]},{"emoji":"๐Ÿ˜†","group":0,"order":5,"tags":["closed","eyes","face","grinning","haha","hahaha","happy","laugh","lol","mouth","open","rofl","smile","smiling","squinting"],"version":0.6,"annotation":"grinning squinting face","emoticon":"XD","shortcodes":["laughing","lol","satisfied","squinting_face"]},{"emoji":"๐Ÿ˜…","group":0,"order":6,"tags":["cold","dejected","excited","face","grinning","mouth","nervous","open","smile","smiling","stress","stressed","sweat"],"version":0.6,"annotation":"grinning face with sweat","shortcodes":["grinning_face_with_sweat","sweat_smile"]},{"emoji":"๐Ÿคฃ","group":0,"order":7,"tags":["crying","face","floor","funny","haha","happy","hehe","hilarious","joy","laugh","lmao","lol","rofl","roflmao","rolling","tear"],"version":3,"annotation":"rolling on the floor laughing","emoticon":":'D","shortcodes":["rofl"]},{"emoji":"๐Ÿ˜‚","group":0,"order":8,"tags":["crying","face","feels","funny","haha","happy","hehe","hilarious","joy","laugh","lmao","lol","rofl","roflmao","tear"],"version":0.6,"annotation":"face with tears of joy","emoticon":":')","shortcodes":["joy","lmao","tears_of_joy"]},{"emoji":"๐Ÿ™‚","group":0,"order":9,"tags":["face","happy","slightly","smile","smiling"],"version":1,"annotation":"slightly smiling face","emoticon":":)","shortcodes":["slightly_smiling_face"]},{"emoji":"๐Ÿ™ƒ","group":0,"order":10,"tags":["face","hehe","smile","upside-down"],"version":1,"annotation":"upside-down face","shortcodes":["upside_down_face"]},{"emoji":"๐Ÿซ ","group":0,"order":11,"tags":["disappear","dissolve","embarrassed","face","haha","heat","hot","liquid","lol","melt","melting","sarcasm","sarcastic"],"version":14,"annotation":"melting face","shortcodes":["melt","melting_face"]},{"emoji":"๐Ÿ˜‰","group":0,"order":12,"tags":["face","flirt","heartbreaker","sexy","slide","tease","wink","winking","winks"],"version":0.6,"annotation":"winking face","emoticon":";)","shortcodes":["wink","winking_face"]},{"emoji":"๐Ÿ˜Š","group":0,"order":13,"tags":["blush","eye","eyes","face","glad","satisfied","smile","smiling"],"version":0.6,"annotation":"smiling face with smiling eyes","emoticon":":>","shortcodes":["blush","smiling_face_with_closed_eyes"]},{"emoji":"๐Ÿ˜‡","group":0,"order":14,"tags":["angel","angelic","angels","blessed","face","fairy","fairytale","fantasy","halo","happy","innocent","peaceful","smile","smiling","spirit","tale"],"version":1,"annotation":"smiling face with halo","emoticon":"O:)","shortcodes":["halo","innocent"]},{"emoji":"๐Ÿฅฐ","group":0,"order":15,"tags":["3","adore","crush","face","heart","hearts","ily","love","romance","smile","smiling","you"],"version":11,"annotation":"smiling face with hearts","shortcodes":["smiling_face_with_3_hearts"]},{"emoji":"๐Ÿ˜","group":0,"order":16,"tags":["143","bae","eye","face","feels","heart-eyes","hearts","ily","kisses","love","romance","romantic","smile","xoxo"],"version":0.6,"annotation":"smiling face with heart-eyes","shortcodes":["heart_eyes","smiling_face_with_heart_eyes"]},{"emoji":"๐Ÿคฉ","group":0,"order":17,"tags":["excited","eyes","face","grinning","smile","star","starry-eyed","wow"],"version":5,"annotation":"star-struck","shortcodes":["star_struck"]},{"emoji":"๐Ÿ˜˜","group":0,"order":18,"tags":["adorbs","bae","blowing","face","flirt","heart","ily","kiss","love","lover","miss","muah","romantic","smooch","xoxo","you"],"version":0.6,"annotation":"face blowing a kiss","emoticon":":X","shortcodes":["blowing_a_kiss","kissing_heart"]},{"emoji":"๐Ÿ˜—","group":0,"order":19,"tags":["143","date","dating","face","flirt","ily","kiss","love","smooch","smooches","xoxo","you"],"version":1,"annotation":"kissing face","shortcodes":["kissing","kissing_face"]},{"emoji":"โ˜บ๏ธ","group":0,"order":21,"tags":["face","happy","outlined","relaxed","smile","smiling"],"version":0.6,"annotation":"smiling face","shortcodes":["relaxed","smiling_face"]},{"emoji":"๐Ÿ˜š","group":0,"order":22,"tags":["143","bae","blush","closed","date","dating","eye","eyes","face","flirt","ily","kisses","kissing","smooches","xoxo"],"version":0.6,"annotation":"kissing face with closed eyes","emoticon":":*","shortcodes":["kissing_closed_eyes","kissing_face_with_closed_eyes"]},{"emoji":"๐Ÿ˜™","group":0,"order":23,"tags":["143","closed","date","dating","eye","eyes","face","flirt","ily","kiss","kisses","kissing","love","night","smile","smiling"],"version":1,"annotation":"kissing face with smiling eyes","shortcodes":["kissing_face_with_smiling_eyes","kissing_smiling_eyes"]},{"emoji":"๐Ÿฅฒ","group":0,"order":24,"tags":["face","glad","grateful","happy","joy","pain","proud","relieved","smile","smiley","smiling","tear","touched"],"version":13,"annotation":"smiling face with tear","shortcodes":["smiling_face_with_tear"]},{"emoji":"๐Ÿ˜‹","group":0,"order":25,"tags":["delicious","eat","face","food","full","hungry","savor","smile","smiling","tasty","um","yum","yummy"],"version":0.6,"annotation":"face savoring food","shortcodes":["savoring_food","yum"]},{"emoji":"๐Ÿ˜›","group":0,"order":26,"tags":["awesome","cool","face","nice","party","stuck-out","sweet","tongue"],"version":1,"annotation":"face with tongue","emoticon":":P","shortcodes":["face_with_tongue","stuck_out_tongue"]},{"emoji":"๐Ÿ˜œ","group":0,"order":27,"tags":["crazy","epic","eye","face","funny","joke","loopy","nutty","party","stuck-out","tongue","wacky","weirdo","wink","winking","yolo"],"version":0.6,"annotation":"winking face with tongue","emoticon":";P","shortcodes":["stuck_out_tongue_winking_eye"]},{"emoji":"๐Ÿคช","group":0,"order":28,"tags":["crazy","eye","eyes","face","goofy","large","small","zany"],"version":5,"annotation":"zany face","shortcodes":["zany","zany_face"]},{"emoji":"๐Ÿ˜","group":0,"order":29,"tags":["closed","eye","eyes","face","gross","horrible","omg","squinting","stuck-out","taste","tongue","whatever","yolo"],"version":0.6,"annotation":"squinting face with tongue","emoticon":"XP","shortcodes":["stuck_out_tongue_closed_eyes"]},{"emoji":"๐Ÿค‘","group":0,"order":30,"tags":["face","money","money-mouth","mouth","paid"],"version":1,"annotation":"money-mouth face","shortcodes":["money_mouth_face"]},{"emoji":"๐Ÿค—","group":0,"order":31,"tags":["face","hands","hug","hugging","open","smiling"],"version":1,"annotation":"smiling face with open hands","shortcodes":["hug","hugging","hugging_face"]},{"emoji":"๐Ÿคญ","group":0,"order":32,"tags":["face","giggle","giggling","hand","mouth","oops","realization","secret","shock","sudden","surprise","whoops"],"version":5,"annotation":"face with hand over mouth","shortcodes":["face_with_hand_over_mouth","hand_over_mouth"]},{"emoji":"๐Ÿซข","group":0,"order":33,"tags":["amazement","awe","disbelief","embarrass","eyes","face","gasp","hand","mouth","omg","open","over","quiet","scared","shock","surprise"],"version":14,"annotation":"face with open eyes and hand over mouth","shortcodes":["face_with_open_eyes_hand_over_mouth","gasp"]},{"emoji":"๐Ÿซฃ","group":0,"order":34,"tags":["captivated","embarrass","eye","face","hide","hiding","peek","peeking","peep","scared","shy","stare"],"version":14,"annotation":"face with peeking eye","shortcodes":["face_with_peeking_eye","peek"]},{"emoji":"๐Ÿคซ","group":0,"order":35,"tags":["face","quiet","shh","shush","shushing"],"version":5,"annotation":"shushing face","shortcodes":["shush","shushing_face"]},{"emoji":"๐Ÿค”","group":0,"order":36,"tags":["chin","consider","face","hmm","ponder","pondering","thinking","wondering"],"version":1,"annotation":"thinking face","emoticon":":L","shortcodes":["thinking","thinking_face","wtf"]},{"emoji":"๐Ÿซก","group":0,"order":37,"tags":["face","good","luck","maโ€™am","ok","respect","salute","saluting","sir","troops","yes"],"version":14,"annotation":"saluting face","shortcodes":["salute","saluting_face"]},{"emoji":"๐Ÿค","group":0,"order":38,"tags":["face","keep","mouth","quiet","secret","shut","zip","zipper","zipper-mouth"],"version":1,"annotation":"zipper-mouth face","emoticon":":Z","shortcodes":["zipper_mouth","zipper_mouth_face"]},{"emoji":"๐Ÿคจ","group":0,"order":39,"tags":["disapproval","disbelief","distrust","emoji","eyebrow","face","hmm","mild","raised","skeptic","skeptical","skepticism","surprise","what"],"version":5,"annotation":"face with raised eyebrow","shortcodes":["face_with_raised_eyebrow","raised_eyebrow"]},{"emoji":"๐Ÿ˜๏ธ","group":0,"order":40,"tags":["awkward","blank","deadpan","expressionless","face","fine","jealous","meh","neutral","oh","shade","straight","unamused","unhappy","unimpressed","whatever"],"version":0.7,"annotation":"neutral face","emoticon":":|","shortcodes":["neutral","neutral_face"]},{"emoji":"๐Ÿ˜‘","group":0,"order":41,"tags":["awkward","dead","expressionless","face","fine","inexpressive","jealous","meh","not","oh","omg","straight","uh","unhappy","unimpressed","whatever"],"version":1,"annotation":"expressionless face","shortcodes":["expressionless","expressionless_face"]},{"emoji":"๐Ÿ˜ถ","group":0,"order":42,"tags":["awkward","blank","expressionless","face","mouth","mouthless","mute","quiet","secret","silence","silent","speechless"],"version":1,"annotation":"face without mouth","emoticon":":#","shortcodes":["no_mouth"]},{"emoji":"๐Ÿซฅ","group":0,"order":43,"tags":["depressed","disappear","dotted","face","hidden","hide","introvert","invisible","line","meh","whatever","wtv"],"version":14,"annotation":"dotted line face","shortcodes":["dotted_line_face"]},{"emoji":"๐Ÿ˜ถโ€๐ŸŒซ๏ธ","group":0,"order":44,"tags":["absentminded","clouds","face","fog","head"],"version":13.1,"annotation":"face in clouds","shortcodes":["in_clouds"]},{"emoji":"๐Ÿ˜","group":0,"order":46,"tags":["boss","dapper","face","flirt","homie","kidding","leer","shade","slick","sly","smirk","smug","snicker","suave","suspicious","swag"],"version":0.6,"annotation":"smirking face","emoticon":":j","shortcodes":["smirk","smirking","smirking_face"]},{"emoji":"๐Ÿ˜’","group":0,"order":47,"tags":["...","bored","face","fine","jealous","jel","jelly","pissed","smh","ugh","uhh","unamused","unhappy","weird","whatever"],"version":0.6,"annotation":"unamused face","emoticon":":?","shortcodes":["unamused","unamused_face"]},{"emoji":"๐Ÿ™„","group":0,"order":48,"tags":["eyeroll","eyes","face","rolling","shade","ugh","whatever"],"version":1,"annotation":"face with rolling eyes","shortcodes":["rolling_eyes"]},{"emoji":"๐Ÿ˜ฌ","group":0,"order":49,"tags":["awk","awkward","dentist","face","grimace","grimacing","grinning","smile","smiling"],"version":1,"annotation":"grimacing face","emoticon":"8D","shortcodes":["grimacing","grimacing_face"]},{"emoji":"๐Ÿ˜ฎโ€๐Ÿ’จ","group":0,"order":50,"tags":["blow","blowing","exhale","exhaling","exhausted","face","gasp","groan","relief","sigh","smiley","smoke","whisper","whistle"],"version":13.1,"annotation":"face exhaling","shortcodes":["exhale","exhaling"]},{"emoji":"๐Ÿคฅ","group":0,"order":51,"tags":["face","liar","lie","lying","pinocchio"],"version":3,"annotation":"lying face","shortcodes":["lying","lying_face"]},{"emoji":"๐Ÿซจ","group":0,"order":52,"tags":["crazy","daze","earthquake","face","omg","panic","shaking","shock","surprise","vibrate","whoa","wow"],"version":15,"annotation":"shaking face","shortcodes":["shaking","shaking_face"]},{"emoji":"๐Ÿ™‚โ€โ†”๏ธ","group":0,"order":53,"tags":["head","horizontally","no","shake","shaking"],"version":15.1,"annotation":"head shaking horizontally","shortcodes":["head_shaking_horizontally"]},{"emoji":"๐Ÿ™‚โ€โ†•๏ธ","group":0,"order":55,"tags":["head","nod","shaking","vertically","yes"],"version":15.1,"annotation":"head shaking vertically","shortcodes":["head_shaking_vertically"]},{"emoji":"๐Ÿ˜Œ","group":0,"order":57,"tags":["calm","face","peace","relief","relieved","zen"],"version":0.6,"annotation":"relieved face","shortcodes":["relieved","relieved_face"]},{"emoji":"๐Ÿ˜”","group":0,"order":58,"tags":["awful","bored","dejected","died","disappointed","face","losing","lost","pensive","sad","sucks"],"version":0.6,"annotation":"pensive face","shortcodes":["pensive","pensive_face"]},{"emoji":"๐Ÿ˜ช","group":0,"order":59,"tags":["crying","face","good","night","sad","sleep","sleeping","sleepy","tired"],"version":0.6,"annotation":"sleepy face","shortcodes":["sleepy","sleepy_face"]},{"emoji":"๐Ÿคค","group":0,"order":60,"tags":["drooling","face"],"version":3,"annotation":"drooling face","shortcodes":["drooling","drooling_face"]},{"emoji":"๐Ÿ˜ด","group":0,"order":61,"tags":["bed","bedtime","face","good","goodnight","nap","night","sleep","sleeping","tired","whatever","yawn","zzz"],"version":1,"annotation":"sleeping face","shortcodes":["sleeping","sleeping_face"]},{"emoji":"๐Ÿซฉ","group":0,"order":62,"tags":["bags","bored","exhausted","eyes","face","fatigued","late","sleepy","tired","weary"],"version":16,"annotation":"face with bags under eyes","shortcodes":["face_with_eye_bags"]},{"emoji":"๐Ÿ˜ท","group":0,"order":63,"tags":["cold","dentist","dermatologist","doctor","dr","face","germs","mask","medical","medicine","sick"],"version":0.6,"annotation":"face with medical mask","shortcodes":["mask","medical_mask"]},{"emoji":"๐Ÿค’","group":0,"order":64,"tags":["face","ill","sick","thermometer"],"version":1,"annotation":"face with thermometer","shortcodes":["face_with_thermometer"]},{"emoji":"๐Ÿค•","group":0,"order":65,"tags":["bandage","face","head-bandage","hurt","injury","ouch"],"version":1,"annotation":"face with head-bandage","shortcodes":["face_with_head_bandage"]},{"emoji":"๐Ÿคข","group":0,"order":66,"tags":["face","gross","nasty","nauseated","sick","vomit"],"version":3,"annotation":"nauseated face","emoticon":"%(","shortcodes":["nauseated","nauseated_face"]},{"emoji":"๐Ÿคฎ","group":0,"order":67,"tags":["barf","ew","face","gross","puke","sick","spew","throw","up","vomit","vomiting"],"version":5,"annotation":"face vomiting","shortcodes":["face_vomiting","vomiting"]},{"emoji":"๐Ÿคง","group":0,"order":68,"tags":["face","fever","flu","gesundheit","sick","sneeze","sneezing"],"version":3,"annotation":"sneezing face","shortcodes":["sneezing","sneezing_face"]},{"emoji":"๐Ÿฅต","group":0,"order":69,"tags":["dying","face","feverish","heat","hot","panting","red-faced","stroke","sweating","tongue"],"version":11,"annotation":"hot face","shortcodes":["hot","hot_face"]},{"emoji":"๐Ÿฅถ","group":0,"order":70,"tags":["blue","blue-faced","cold","face","freezing","frostbite","icicles","subzero","teeth"],"version":11,"annotation":"cold face","shortcodes":["cold","cold_face"]},{"emoji":"๐Ÿฅด","group":0,"order":71,"tags":["dizzy","drunk","eyes","face","intoxicated","mouth","tipsy","uneven","wavy","woozy"],"version":11,"annotation":"woozy face","emoticon":":&","shortcodes":["woozy","woozy_face"]},{"emoji":"๐Ÿ˜ต","group":0,"order":72,"tags":["crossed-out","dead","dizzy","eyes","face","feels","knocked","out","sick","tired"],"version":0.6,"annotation":"face with crossed-out eyes","emoticon":"XO","shortcodes":["dizzy_face","knocked_out"]},{"emoji":"๐Ÿ˜ตโ€๐Ÿ’ซ","group":0,"order":73,"tags":["confused","dizzy","eyes","face","hypnotized","omg","smiley","spiral","trouble","whoa","woah","woozy"],"version":13.1,"annotation":"face with spiral eyes","shortcodes":["dizzy_eyes"]},{"emoji":"๐Ÿคฏ","group":0,"order":74,"tags":["blown","explode","exploding","head","mind","mindblown","no","shocked","way"],"version":5,"annotation":"exploding head","shortcodes":["exploding_head"]},{"emoji":"๐Ÿค ","group":0,"order":75,"tags":["cowboy","cowgirl","face","hat"],"version":3,"annotation":"cowboy hat face","shortcodes":["cowboy","cowboy_face"]},{"emoji":"๐Ÿฅณ","group":0,"order":76,"tags":["bday","birthday","celebrate","celebration","excited","face","happy","hat","hooray","horn","party","partying"],"version":11,"annotation":"partying face","shortcodes":["hooray","partying","partying_face"]},{"emoji":"๐Ÿฅธ","group":0,"order":77,"tags":["disguise","eyebrow","face","glasses","incognito","moustache","mustache","nose","person","spy","tache","tash"],"version":13,"annotation":"disguised face","shortcodes":["disguised","disguised_face"]},{"emoji":"๐Ÿ˜Ž","group":0,"order":78,"tags":["awesome","beach","bright","bro","chilling","cool","face","rad","relaxed","shades","slay","smile","style","sunglasses","swag","win"],"version":1,"annotation":"smiling face with sunglasses","emoticon":"8)","shortcodes":["smiling_face_with_sunglasses","sunglasses_cool","too_cool"]},{"emoji":"๐Ÿค“","group":0,"order":79,"tags":["brainy","clever","expert","face","geek","gifted","glasses","intelligent","nerd","smart"],"version":1,"annotation":"nerd face","emoticon":":B","shortcodes":["nerd","nerd_face"]},{"emoji":"๐Ÿง","group":0,"order":80,"tags":["classy","face","fancy","monocle","rich","stuffy","wealthy"],"version":5,"annotation":"face with monocle","shortcodes":["face_with_monocle"]},{"emoji":"๐Ÿ˜•","group":0,"order":81,"tags":["befuddled","confused","confusing","dunno","face","frown","hm","meh","not","sad","sorry","sure"],"version":1,"annotation":"confused face","emoticon":":/","shortcodes":["confused","confused_face"]},{"emoji":"๐Ÿซค","group":0,"order":82,"tags":["confused","confusion","diagonal","disappointed","doubt","doubtful","face","frustrated","frustration","meh","mouth","skeptical","unsure","whatever","wtv"],"version":14,"annotation":"face with diagonal mouth","shortcodes":["face_with_diagonal_mouth"]},{"emoji":"๐Ÿ˜Ÿ","group":0,"order":83,"tags":["anxious","butterflies","face","nerves","nervous","sad","stress","stressed","surprised","worried","worry"],"version":1,"annotation":"worried face","shortcodes":["worried","worried_face"]},{"emoji":"๐Ÿ™","group":0,"order":84,"tags":["face","frown","frowning","sad","slightly"],"version":1,"annotation":"slightly frowning face","shortcodes":["slightly_frowning_face"]},{"emoji":"โ˜น๏ธ","group":0,"order":86,"tags":["face","frown","frowning","sad"],"version":0.7,"annotation":"frowning face","emoticon":":(","shortcodes":["white_frowning_face"]},{"emoji":"๐Ÿ˜ฎ","group":0,"order":87,"tags":["believe","face","forgot","mouth","omg","open","shocked","surprised","sympathy","unbelievable","unreal","whoa","wow","you"],"version":1,"annotation":"face with open mouth","shortcodes":["face_with_open_mouth","open_mouth"]},{"emoji":"๐Ÿ˜ฏ","group":0,"order":88,"tags":["epic","face","hushed","omg","stunned","surprised","whoa","woah"],"version":1,"annotation":"hushed face","shortcodes":["hushed","hushed_face"]},{"emoji":"๐Ÿ˜ฒ","group":0,"order":89,"tags":["astonished","cost","face","no","omg","shocked","totally","way"],"version":0.6,"annotation":"astonished face","emoticon":":O","shortcodes":["astonished","astonished_face"]},{"emoji":"๐Ÿ˜ณ","group":0,"order":90,"tags":["amazed","awkward","crazy","dazed","dead","disbelief","embarrassed","face","flushed","geez","heat","hot","impressed","jeez","what","wow"],"version":0.6,"annotation":"flushed face","emoticon":":$","shortcodes":["flushed","flushed_face"]},{"emoji":"๐Ÿฅบ","group":0,"order":91,"tags":["begging","big","eyes","face","mercy","not","pleading","please","pretty","puppy","sad","why"],"version":11,"annotation":"pleading face","shortcodes":["pleading","pleading_face"]},{"emoji":"๐Ÿฅน","group":0,"order":92,"tags":["admiration","aww","back","cry","embarrassed","face","feelings","grateful","gratitude","holding","joy","please","proud","resist","sad","tears"],"version":14,"annotation":"face holding back tears","shortcodes":["face_holding_back_tears","watery_eyes"]},{"emoji":"๐Ÿ˜ฆ","group":0,"order":93,"tags":["caught","face","frown","frowning","guard","mouth","open","scared","scary","surprise","what","wow"],"version":1,"annotation":"frowning face with open mouth","shortcodes":["frowning","frowning_face"]},{"emoji":"๐Ÿ˜ง","group":0,"order":94,"tags":["anguished","face","forgot","scared","scary","stressed","surprise","unhappy","what","wow"],"version":1,"annotation":"anguished face","emoticon":":S","shortcodes":["anguished","anguished_face"]},{"emoji":"๐Ÿ˜จ","group":0,"order":95,"tags":["afraid","anxious","blame","face","fear","fearful","scared","worried"],"version":0.6,"annotation":"fearful face","shortcodes":["fearful","fearful_face"]},{"emoji":"๐Ÿ˜ฐ","group":0,"order":96,"tags":["anxious","blue","cold","eek","face","mouth","nervous","open","rushed","scared","sweat","yikes"],"version":0.6,"annotation":"anxious face with sweat","shortcodes":["anxious","anxious_face","cold_sweat"]},{"emoji":"๐Ÿ˜ฅ","group":0,"order":97,"tags":["anxious","call","close","complicated","disappointed","face","not","relieved","sad","sweat","time","whew"],"version":0.6,"annotation":"sad but relieved face","shortcodes":["disappointed_relieved","sad_relieved_face"]},{"emoji":"๐Ÿ˜ข","group":0,"order":98,"tags":["awful","cry","crying","face","feels","miss","sad","tear","triste","unhappy"],"version":0.6,"annotation":"crying face","emoticon":":'(","shortcodes":["cry","crying_face"]},{"emoji":"๐Ÿ˜ญ","group":0,"order":99,"tags":["bawling","cry","crying","face","loudly","sad","sob","tear","tears","unhappy"],"version":0.6,"annotation":"loudly crying face","emoticon":":'o","shortcodes":["loudly_crying_face","sob"]},{"emoji":"๐Ÿ˜ฑ","group":0,"order":100,"tags":["epic","face","fear","fearful","munch","scared","scream","screamer","screaming","shocked","surprised","woah"],"version":0.6,"annotation":"face screaming in fear","emoticon":"Dx","shortcodes":["scream","screaming_in_fear"]},{"emoji":"๐Ÿ˜–","group":0,"order":101,"tags":["annoyed","confounded","confused","cringe","distraught","face","feels","frustrated","mad","sad"],"version":0.6,"annotation":"confounded face","emoticon":"X(","shortcodes":["confounded","confounded_face"]},{"emoji":"๐Ÿ˜ฃ","group":0,"order":102,"tags":["concentrate","concentration","face","focus","headache","persevere","persevering"],"version":0.6,"annotation":"persevering face","shortcodes":["persevere","persevering_face"]},{"emoji":"๐Ÿ˜ž","group":0,"order":103,"tags":["awful","blame","dejected","disappointed","face","fail","losing","sad","unhappy"],"version":0.6,"annotation":"disappointed face","shortcodes":["disappointed","disappointed_face"]},{"emoji":"๐Ÿ˜“","group":0,"order":104,"tags":["close","cold","downcast","face","feels","headache","nervous","sad","scared","sweat","yikes"],"version":0.6,"annotation":"downcast face with sweat","emoticon":":<","shortcodes":["downcast_face","sweat"]},{"emoji":"๐Ÿ˜ฉ","group":0,"order":105,"tags":["crying","face","fail","feels","hungry","mad","nooo","sad","sleepy","tired","unhappy","weary"],"version":0.6,"annotation":"weary face","emoticon":"D:","shortcodes":["weary","weary_face"]},{"emoji":"๐Ÿ˜ซ","group":0,"order":106,"tags":["cost","face","feels","nap","sad","sneeze","tired"],"version":0.6,"annotation":"tired face","emoticon":":C","shortcodes":["tired","tired_face"]},{"emoji":"๐Ÿฅฑ","group":0,"order":107,"tags":["bedtime","bored","face","goodnight","nap","night","sleep","sleepy","tired","whatever","yawn","yawning","zzz"],"version":12,"annotation":"yawning face","shortcodes":["yawn","yawning","yawning_face"]},{"emoji":"๐Ÿ˜ค","group":0,"order":108,"tags":["anger","angry","face","feels","fume","fuming","furious","fury","mad","nose","steam","triumph","unhappy","won"],"version":0.6,"annotation":"face with steam from nose","shortcodes":["nose_steam","triumph"]},{"emoji":"๐Ÿ˜ก","group":0,"order":109,"tags":["anger","angry","enraged","face","feels","mad","maddening","pouting","rage","red","shade","unhappy","upset"],"version":0.6,"annotation":"enraged face","emoticon":">:/","shortcodes":["pout","pouting_face","rage"]},{"emoji":"๐Ÿ˜ ","group":0,"order":110,"tags":["anger","angry","blame","face","feels","frustrated","mad","maddening","rage","shade","unhappy","upset"],"version":0.6,"annotation":"angry face","shortcodes":["angry","angry_face"]},{"emoji":"๐Ÿคฌ","group":0,"order":111,"tags":["censor","cursing","cussing","face","mad","mouth","pissed","swearing","symbols"],"version":5,"annotation":"face with symbols on mouth","emoticon":":@","shortcodes":["censored","face_with_symbols_on_mouth"]},{"emoji":"๐Ÿ˜ˆ","group":0,"order":112,"tags":["demon","devil","evil","face","fairy","fairytale","fantasy","horns","purple","shade","smile","smiling","tale"],"version":1,"annotation":"smiling face with horns","emoticon":">:)","shortcodes":["smiling_imp"]},{"emoji":"๐Ÿ‘ฟ","group":0,"order":113,"tags":["angry","demon","devil","evil","face","fairy","fairytale","fantasy","horns","imp","mischievous","purple","shade","tale"],"version":0.6,"annotation":"angry face with horns","emoticon":">:(","shortcodes":["angry_imp","imp"]},{"emoji":"๐Ÿ’€","group":0,"order":114,"tags":["body","dead","death","face","fairy","fairytale","iโ€™m","lmao","monster","tale","yolo"],"version":0.6,"annotation":"skull","shortcodes":["skull"]},{"emoji":"โ˜ ๏ธ","group":0,"order":116,"tags":["bone","crossbones","dead","death","face","monster","skull"],"version":1,"annotation":"skull and crossbones","shortcodes":["skull_and_crossbones"]},{"emoji":"๐Ÿ’ฉ","group":0,"order":117,"tags":["bs","comic","doo","dung","face","fml","monster","pile","poo","poop","smelly","smh","stink","stinks","stinky","turd"],"version":0.6,"annotation":"pile of poo","shortcodes":["poop","shit"]},{"emoji":"๐Ÿคก","group":0,"order":118,"tags":["clown","face"],"version":3,"annotation":"clown face","shortcodes":["clown","clown_face"]},{"emoji":"๐Ÿ‘น","group":0,"order":119,"tags":["creature","devil","face","fairy","fairytale","fantasy","mask","monster","scary","tale"],"version":0.6,"annotation":"ogre","emoticon":">0)","shortcodes":["japanese_ogre","ogre"]},{"emoji":"๐Ÿ‘บ","group":0,"order":120,"tags":["angry","creature","face","fairy","fairytale","fantasy","mask","mean","monster","tale"],"version":0.6,"annotation":"goblin","shortcodes":["goblin","japanese_goblin"]},{"emoji":"๐Ÿ‘ป","group":0,"order":121,"tags":["boo","creature","excited","face","fairy","fairytale","fantasy","halloween","haunting","monster","scary","silly","tale"],"version":0.6,"annotation":"ghost","shortcodes":["ghost"]},{"emoji":"๐Ÿ‘ฝ๏ธ","group":0,"order":122,"tags":["creature","extraterrestrial","face","fairy","fairytale","fantasy","monster","space","tale","ufo"],"version":0.6,"annotation":"alien","shortcodes":["alien"]},{"emoji":"๐Ÿ‘พ","group":0,"order":123,"tags":["alien","creature","extraterrestrial","face","fairy","fairytale","fantasy","game","gamer","games","monster","pixelated","space","tale","ufo"],"version":0.6,"annotation":"alien monster","shortcodes":["alien_monster","space_invader"]},{"emoji":"๐Ÿค–","group":0,"order":124,"tags":["face","monster"],"version":1,"annotation":"robot","shortcodes":["robot","robot_face"]},{"emoji":"๐Ÿ˜บ","group":0,"order":125,"tags":["animal","cat","face","grinning","mouth","open","smile","smiling"],"version":0.6,"annotation":"grinning cat","shortcodes":["grinning_cat","smiley_cat"]},{"emoji":"๐Ÿ˜ธ","group":0,"order":126,"tags":["animal","cat","eye","eyes","face","grin","grinning","smile","smiling"],"version":0.6,"annotation":"grinning cat with smiling eyes","shortcodes":["grinning_cat_with_closed_eyes","smile_cat"]},{"emoji":"๐Ÿ˜น","group":0,"order":127,"tags":["animal","cat","face","joy","laugh","laughing","lol","tear","tears"],"version":0.6,"annotation":"cat with tears of joy","shortcodes":["joy_cat","tears_of_joy_cat"]},{"emoji":"๐Ÿ˜ป","group":0,"order":128,"tags":["animal","cat","eye","face","heart","heart-eyes","love","smile","smiling"],"version":0.6,"annotation":"smiling cat with heart-eyes","shortcodes":["heart_eyes_cat","smiling_cat_with_heart_eyes"]},{"emoji":"๐Ÿ˜ผ","group":0,"order":129,"tags":["animal","cat","face","ironic","smile","wry"],"version":0.6,"annotation":"cat with wry smile","shortcodes":["smirk_cat","wry_smile_cat"]},{"emoji":"๐Ÿ˜ฝ","group":0,"order":130,"tags":["animal","cat","closed","eye","eyes","face","kiss","kissing"],"version":0.6,"annotation":"kissing cat","emoticon":":3","shortcodes":["kissing_cat"]},{"emoji":"๐Ÿ™€","group":0,"order":131,"tags":["animal","cat","face","oh","surprised","weary"],"version":0.6,"annotation":"weary cat","shortcodes":["scream_cat","weary_cat"]},{"emoji":"๐Ÿ˜ฟ","group":0,"order":132,"tags":["animal","cat","cry","crying","face","sad","tear"],"version":0.6,"annotation":"crying cat","shortcodes":["crying_cat"]},{"emoji":"๐Ÿ˜พ","group":0,"order":133,"tags":["animal","cat","face","pouting"],"version":0.6,"annotation":"pouting cat","shortcodes":["pouting_cat"]},{"emoji":"๐Ÿ™ˆ","group":0,"order":134,"tags":["embarrassed","evil","face","forbidden","forgot","gesture","hide","monkey","no","omg","prohibited","scared","secret","smh","watch"],"version":0.6,"annotation":"see-no-evil monkey","shortcodes":["see_no_evil"]},{"emoji":"๐Ÿ™‰","group":0,"order":135,"tags":["animal","ears","evil","face","forbidden","gesture","hear","listen","monkey","no","not","prohibited","secret","shh","tmi"],"version":0.6,"annotation":"hear-no-evil monkey","shortcodes":["hear_no_evil"]},{"emoji":"๐Ÿ™Š","group":0,"order":136,"tags":["animal","evil","face","forbidden","gesture","monkey","no","not","oops","prohibited","quiet","secret","speak","stealth"],"version":0.6,"annotation":"speak-no-evil monkey","shortcodes":["speak_no_evil"]},{"emoji":"๐Ÿ’Œ","group":0,"order":137,"tags":["heart","letter","love","mail","romance","valentine"],"version":0.6,"annotation":"love letter","shortcodes":["love_letter"]},{"emoji":"๐Ÿ’˜","group":0,"order":138,"tags":["143","adorbs","arrow","cupid","date","emotion","heart","ily","love","romance","valentine"],"version":0.6,"annotation":"heart with arrow","shortcodes":["cupid","heart_with_arrow"]},{"emoji":"๐Ÿ’","group":0,"order":139,"tags":["143","anniversary","emotion","heart","ily","kisses","ribbon","valentine","xoxo"],"version":0.6,"annotation":"heart with ribbon","shortcodes":["gift_heart","heart_with_ribbon"]},{"emoji":"๐Ÿ’–","group":0,"order":140,"tags":["143","emotion","excited","good","heart","ily","kisses","morning","night","sparkle","sparkling","xoxo"],"version":0.6,"annotation":"sparkling heart","shortcodes":["sparkling_heart"]},{"emoji":"๐Ÿ’—","group":0,"order":141,"tags":["143","emotion","excited","growing","heart","heartpulse","ily","kisses","muah","nervous","pulse","xoxo"],"version":0.6,"annotation":"growing heart","shortcodes":["growing_heart","heartpulse"]},{"emoji":"๐Ÿ’“","group":0,"order":142,"tags":["143","beating","cardio","emotion","heart","heartbeat","ily","love","pulsating","pulse"],"version":0.6,"annotation":"beating heart","shortcodes":["beating_heart","heartbeat"]},{"emoji":"๐Ÿ’ž","group":0,"order":143,"tags":["143","adorbs","anniversary","emotion","heart","hearts","revolving"],"version":0.6,"annotation":"revolving hearts","shortcodes":["revolving_hearts"]},{"emoji":"๐Ÿ’•","group":0,"order":144,"tags":["143","anniversary","date","dating","emotion","heart","hearts","ily","kisses","love","loving","two","xoxo"],"version":0.6,"annotation":"two hearts","shortcodes":["two_hearts"]},{"emoji":"๐Ÿ’Ÿ","group":0,"order":145,"tags":["143","decoration","emotion","heart","hearth","purple","white"],"version":0.6,"annotation":"heart decoration","shortcodes":["heart_decoration"]},{"emoji":"โฃ๏ธ","group":0,"order":147,"tags":["exclamation","heart","heavy","mark","punctuation"],"version":1,"annotation":"heart exclamation","shortcodes":["heart_exclamation"]},{"emoji":"๐Ÿ’”","group":0,"order":148,"tags":["break","broken","crushed","emotion","heart","heartbroken","lonely","sad"],"version":0.6,"annotation":"broken heart","emoticon":"","shortcodes":["man_mage"],"skins":[{"tone":1,"emoji":"๐Ÿง™๐Ÿปโ€โ™‚๏ธ","version":5},{"tone":2,"emoji":"๐Ÿง™๐Ÿผโ€โ™‚๏ธ","version":5},{"tone":3,"emoji":"๐Ÿง™๐Ÿฝโ€โ™‚๏ธ","version":5},{"tone":4,"emoji":"๐Ÿง™๐Ÿพโ€โ™‚๏ธ","version":5},{"tone":5,"emoji":"๐Ÿง™๐Ÿฟโ€โ™‚๏ธ","version":5}]},{"emoji":"๐Ÿง™โ€โ™€๏ธ","group":1,"order":1746,"tags":["fantasy","mage","magic","play","sorcerer","sorceress","sorcery","spell","summon","witch","wizard","woman"],"version":5,"annotation":"woman mage","shortcodes":["woman_mage"],"skins":[{"tone":1,"emoji":"๐Ÿง™๐Ÿปโ€โ™€๏ธ","version":5},{"tone":2,"emoji":"๐Ÿง™๐Ÿผโ€โ™€๏ธ","version":5},{"tone":3,"emoji":"๐Ÿง™๐Ÿฝโ€โ™€๏ธ","version":5},{"tone":4,"emoji":"๐Ÿง™๐Ÿพโ€โ™€๏ธ","version":5},{"tone":5,"emoji":"๐Ÿง™๐Ÿฟโ€โ™€๏ธ","version":5}]},{"emoji":"๐Ÿงš","group":1,"order":1758,"tags":["fairytale","fantasy","myth","person","pixie","tale","wings"],"version":5,"annotation":"fairy","shortcodes":["fairy"],"skins":[{"tone":1,"emoji":"๐Ÿงš๐Ÿป","version":5},{"tone":2,"emoji":"๐Ÿงš๐Ÿผ","version":5},{"tone":3,"emoji":"๐Ÿงš๐Ÿฝ","version":5},{"tone":4,"emoji":"๐Ÿงš๐Ÿพ","version":5},{"tone":5,"emoji":"๐Ÿงš๐Ÿฟ","version":5}]},{"emoji":"๐Ÿงšโ€โ™‚๏ธ","group":1,"order":1764,"tags":["fairy","fairytale","fantasy","man","myth","oberon","person","pixie","puck","tale","wings"],"version":5,"annotation":"man fairy","shortcodes":["man_fairy"],"skins":[{"tone":1,"emoji":"๐Ÿงš๐Ÿปโ€โ™‚๏ธ","version":5},{"tone":2,"emoji":"๐Ÿงš๐Ÿผโ€โ™‚๏ธ","version":5},{"tone":3,"emoji":"๐Ÿงš๐Ÿฝโ€โ™‚๏ธ","version":5},{"tone":4,"emoji":"๐Ÿงš๐Ÿพโ€โ™‚๏ธ","version":5},{"tone":5,"emoji":"๐Ÿงš๐Ÿฟโ€โ™‚๏ธ","version":5}]},{"emoji":"๐Ÿงšโ€โ™€๏ธ","group":1,"order":1776,"tags":["fairy","fairytale","fantasy","myth","person","pixie","tale","titania","wings","woman"],"version":5,"annotation":"woman fairy","shortcodes":["woman_fairy"],"skins":[{"tone":1,"emoji":"๐Ÿงš๐Ÿปโ€โ™€๏ธ","version":5},{"tone":2,"emoji":"๐Ÿงš๐Ÿผโ€โ™€๏ธ","version":5},{"tone":3,"emoji":"๐Ÿงš๐Ÿฝโ€โ™€๏ธ","version":5},{"tone":4,"emoji":"๐Ÿงš๐Ÿพโ€โ™€๏ธ","version":5},{"tone":5,"emoji":"๐Ÿงš๐Ÿฟโ€โ™€๏ธ","version":5}]},{"emoji":"๐Ÿง›","group":1,"order":1788,"tags":["blood","dracula","fangs","halloween","scary","supernatural","teeth","undead"],"version":5,"annotation":"vampire","emoticon":":E","shortcodes":["vampire"],"skins":[{"tone":1,"emoji":"๐Ÿง›๐Ÿป","version":5},{"tone":2,"emoji":"๐Ÿง›๐Ÿผ","version":5},{"tone":3,"emoji":"๐Ÿง›๐Ÿฝ","version":5},{"tone":4,"emoji":"๐Ÿง›๐Ÿพ","version":5},{"tone":5,"emoji":"๐Ÿง›๐Ÿฟ","version":5}]},{"emoji":"๐Ÿง›โ€โ™‚๏ธ","group":1,"order":1794,"tags":["blood","fangs","halloween","man","scary","supernatural","teeth","undead","vampire"],"version":5,"annotation":"man vampire","shortcodes":["man_vampire"],"skins":[{"tone":1,"emoji":"๐Ÿง›๐Ÿปโ€โ™‚๏ธ","version":5},{"tone":2,"emoji":"๐Ÿง›๐Ÿผโ€โ™‚๏ธ","version":5},{"tone":3,"emoji":"๐Ÿง›๐Ÿฝโ€โ™‚๏ธ","version":5},{"tone":4,"emoji":"๐Ÿง›๐Ÿพโ€โ™‚๏ธ","version":5},{"tone":5,"emoji":"๐Ÿง›๐Ÿฟโ€โ™‚๏ธ","version":5}]},{"emoji":"๐Ÿง›โ€โ™€๏ธ","group":1,"order":1806,"tags":["blood","fangs","halloween","scary","supernatural","teeth","undead","vampire","woman"],"version":5,"annotation":"woman vampire","shortcodes":["woman_vampire"],"skins":[{"tone":1,"emoji":"๐Ÿง›๐Ÿปโ€โ™€๏ธ","version":5},{"tone":2,"emoji":"๐Ÿง›๐Ÿผโ€โ™€๏ธ","version":5},{"tone":3,"emoji":"๐Ÿง›๐Ÿฝโ€โ™€๏ธ","version":5},{"tone":4,"emoji":"๐Ÿง›๐Ÿพโ€โ™€๏ธ","version":5},{"tone":5,"emoji":"๐Ÿง›๐Ÿฟโ€โ™€๏ธ","version":5}]},{"emoji":"๐Ÿงœ","group":1,"order":1818,"tags":["creature","fairytale","folklore","ocean","sea","siren","trident"],"version":5,"annotation":"merperson","shortcodes":["merperson"],"skins":[{"tone":1,"emoji":"๐Ÿงœ๐Ÿป","version":5},{"tone":2,"emoji":"๐Ÿงœ๐Ÿผ","version":5},{"tone":3,"emoji":"๐Ÿงœ๐Ÿฝ","version":5},{"tone":4,"emoji":"๐Ÿงœ๐Ÿพ","version":5},{"tone":5,"emoji":"๐Ÿงœ๐Ÿฟ","version":5}]},{"emoji":"๐Ÿงœโ€โ™‚๏ธ","group":1,"order":1824,"tags":["creature","fairytale","folklore","neptune","ocean","poseidon","sea","siren","trident","triton"],"version":5,"annotation":"merman","shortcodes":["merman"],"skins":[{"tone":1,"emoji":"๐Ÿงœ๐Ÿปโ€โ™‚๏ธ","version":5},{"tone":2,"emoji":"๐Ÿงœ๐Ÿผโ€โ™‚๏ธ","version":5},{"tone":3,"emoji":"๐Ÿงœ๐Ÿฝโ€โ™‚๏ธ","version":5},{"tone":4,"emoji":"๐Ÿงœ๐Ÿพโ€โ™‚๏ธ","version":5},{"tone":5,"emoji":"๐Ÿงœ๐Ÿฟโ€โ™‚๏ธ","version":5}]},{"emoji":"๐Ÿงœโ€โ™€๏ธ","group":1,"order":1836,"tags":["creature","fairytale","folklore","merwoman","ocean","sea","siren","trident"],"version":5,"annotation":"mermaid","shortcodes":["mermaid"],"skins":[{"tone":1,"emoji":"๐Ÿงœ๐Ÿปโ€โ™€๏ธ","version":5},{"tone":2,"emoji":"๐Ÿงœ๐Ÿผโ€โ™€๏ธ","version":5},{"tone":3,"emoji":"๐Ÿงœ๐Ÿฝโ€โ™€๏ธ","version":5},{"tone":4,"emoji":"๐Ÿงœ๐Ÿพโ€โ™€๏ธ","version":5},{"tone":5,"emoji":"๐Ÿงœ๐Ÿฟโ€โ™€๏ธ","version":5}]},{"emoji":"๐Ÿง","group":1,"order":1848,"tags":["elves","enchantment","fantasy","folklore","magic","magical","myth"],"version":5,"annotation":"elf","shortcodes":["elf"],"skins":[{"tone":1,"emoji":"๐Ÿง๐Ÿป","version":5},{"tone":2,"emoji":"๐Ÿง๐Ÿผ","version":5},{"tone":3,"emoji":"๐Ÿง๐Ÿฝ","version":5},{"tone":4,"emoji":"๐Ÿง๐Ÿพ","version":5},{"tone":5,"emoji":"๐Ÿง๐Ÿฟ","version":5}]},{"emoji":"๐Ÿงโ€โ™‚๏ธ","group":1,"order":1854,"tags":["elf","elves","enchantment","fantasy","folklore","magic","magical","man","myth"],"version":5,"annotation":"man elf","shortcodes":["man_elf"],"skins":[{"tone":1,"emoji":"๐Ÿง๐Ÿปโ€โ™‚๏ธ","version":5},{"tone":2,"emoji":"๐Ÿง๐Ÿผโ€โ™‚๏ธ","version":5},{"tone":3,"emoji":"๐Ÿง๐Ÿฝโ€โ™‚๏ธ","version":5},{"tone":4,"emoji":"๐Ÿง๐Ÿพโ€โ™‚๏ธ","version":5},{"tone":5,"emoji":"๐Ÿง๐Ÿฟโ€โ™‚๏ธ","version":5}]},{"emoji":"๐Ÿงโ€โ™€๏ธ","group":1,"order":1866,"tags":["elf","elves","enchantment","fantasy","folklore","magic","magical","myth","woman"],"version":5,"annotation":"woman elf","shortcodes":["woman_elf"],"skins":[{"tone":1,"emoji":"๐Ÿง๐Ÿปโ€โ™€๏ธ","version":5},{"tone":2,"emoji":"๐Ÿง๐Ÿผโ€โ™€๏ธ","version":5},{"tone":3,"emoji":"๐Ÿง๐Ÿฝโ€โ™€๏ธ","version":5},{"tone":4,"emoji":"๐Ÿง๐Ÿพโ€โ™€๏ธ","version":5},{"tone":5,"emoji":"๐Ÿง๐Ÿฟโ€โ™€๏ธ","version":5}]},{"emoji":"๐Ÿงž","group":1,"order":1878,"tags":["djinn","fantasy","jinn","lamp","myth","rub","wishes"],"version":5,"annotation":"genie","shortcodes":["genie"]},{"emoji":"๐Ÿงžโ€โ™‚๏ธ","group":1,"order":1879,"tags":["djinn","fantasy","genie","jinn","lamp","man","myth","rub","wishes"],"version":5,"annotation":"man genie","shortcodes":["man_genie"]},{"emoji":"๐Ÿงžโ€โ™€๏ธ","group":1,"order":1881,"tags":["djinn","fantasy","genie","jinn","lamp","myth","rub","wishes","woman"],"version":5,"annotation":"woman genie","shortcodes":["woman_genie"]},{"emoji":"๐ŸงŸ","group":1,"order":1883,"tags":["apocalypse","dead","halloween","horror","scary","undead","walking"],"version":5,"annotation":"zombie","emoticon":"8#","shortcodes":["zombie"]},{"emoji":"๐ŸงŸโ€โ™‚๏ธ","group":1,"order":1884,"tags":["apocalypse","dead","halloween","horror","man","scary","undead","walking","zombie"],"version":5,"annotation":"man zombie","shortcodes":["man_zombie"]},{"emoji":"๐ŸงŸโ€โ™€๏ธ","group":1,"order":1886,"tags":["apocalypse","dead","halloween","horror","scary","undead","walking","woman","zombie"],"version":5,"annotation":"woman zombie","shortcodes":["woman_zombie"]},{"emoji":"๐ŸงŒ","group":1,"order":1888,"tags":["fairy","fantasy","monster","tale","trolling"],"version":14,"annotation":"troll","shortcodes":["troll"]},{"emoji":"๐Ÿ’†","group":1,"order":1889,"tags":["face","getting","headache","massage","person","relax","relaxing","salon","soothe","spa","tension","therapy","treatment"],"version":0.6,"annotation":"person getting massage","shortcodes":["massage","person_getting_massage"],"skins":[{"tone":1,"emoji":"๐Ÿ’†๐Ÿป","version":1},{"tone":2,"emoji":"๐Ÿ’†๐Ÿผ","version":1},{"tone":3,"emoji":"๐Ÿ’†๐Ÿฝ","version":1},{"tone":4,"emoji":"๐Ÿ’†๐Ÿพ","version":1},{"tone":5,"emoji":"๐Ÿ’†๐Ÿฟ","version":1}]},{"emoji":"๐Ÿ’†โ€โ™‚๏ธ","group":1,"order":1895,"tags":["face","getting","headache","man","massage","relax","relaxing","salon","soothe","spa","tension","therapy","treatment"],"version":4,"annotation":"man getting massage","shortcodes":["man_getting_massage"],"skins":[{"tone":1,"emoji":"๐Ÿ’†๐Ÿปโ€โ™‚๏ธ","version":4},{"tone":2,"emoji":"๐Ÿ’†๐Ÿผโ€โ™‚๏ธ","version":4},{"tone":3,"emoji":"๐Ÿ’†๐Ÿฝโ€โ™‚๏ธ","version":4},{"tone":4,"emoji":"๐Ÿ’†๐Ÿพโ€โ™‚๏ธ","version":4},{"tone":5,"emoji":"๐Ÿ’†๐Ÿฟโ€โ™‚๏ธ","version":4}]},{"emoji":"๐Ÿ’†โ€โ™€๏ธ","group":1,"order":1907,"tags":["face","getting","headache","massage","relax","relaxing","salon","soothe","spa","tension","therapy","treatment","woman"],"version":4,"annotation":"woman getting massage","shortcodes":["woman_getting_massage"],"skins":[{"tone":1,"emoji":"๐Ÿ’†๐Ÿปโ€โ™€๏ธ","version":4},{"tone":2,"emoji":"๐Ÿ’†๐Ÿผโ€โ™€๏ธ","version":4},{"tone":3,"emoji":"๐Ÿ’†๐Ÿฝโ€โ™€๏ธ","version":4},{"tone":4,"emoji":"๐Ÿ’†๐Ÿพโ€โ™€๏ธ","version":4},{"tone":5,"emoji":"๐Ÿ’†๐Ÿฟโ€โ™€๏ธ","version":4}]},{"emoji":"๐Ÿ’‡","group":1,"order":1919,"tags":["barber","beauty","chop","cosmetology","cut","groom","hair","haircut","parlor","person","shears","style"],"version":0.6,"annotation":"person getting haircut","shortcodes":["haircut","person_getting_haircut"],"skins":[{"tone":1,"emoji":"๐Ÿ’‡๐Ÿป","version":1},{"tone":2,"emoji":"๐Ÿ’‡๐Ÿผ","version":1},{"tone":3,"emoji":"๐Ÿ’‡๐Ÿฝ","version":1},{"tone":4,"emoji":"๐Ÿ’‡๐Ÿพ","version":1},{"tone":5,"emoji":"๐Ÿ’‡๐Ÿฟ","version":1}]},{"emoji":"๐Ÿ’‡โ€โ™‚๏ธ","group":1,"order":1925,"tags":["barber","beauty","chop","cosmetology","cut","groom","hair","haircut","man","parlor","person","shears","style"],"version":4,"annotation":"man getting haircut","shortcodes":["man_getting_haircut"],"skins":[{"tone":1,"emoji":"๐Ÿ’‡๐Ÿปโ€โ™‚๏ธ","version":4},{"tone":2,"emoji":"๐Ÿ’‡๐Ÿผโ€โ™‚๏ธ","version":4},{"tone":3,"emoji":"๐Ÿ’‡๐Ÿฝโ€โ™‚๏ธ","version":4},{"tone":4,"emoji":"๐Ÿ’‡๐Ÿพโ€โ™‚๏ธ","version":4},{"tone":5,"emoji":"๐Ÿ’‡๐Ÿฟโ€โ™‚๏ธ","version":4}]},{"emoji":"๐Ÿ’‡โ€โ™€๏ธ","group":1,"order":1937,"tags":["barber","beauty","chop","cosmetology","cut","groom","hair","haircut","parlor","person","shears","style","woman"],"version":4,"annotation":"woman getting haircut","shortcodes":["woman_getting_haircut"],"skins":[{"tone":1,"emoji":"๐Ÿ’‡๐Ÿปโ€โ™€๏ธ","version":4},{"tone":2,"emoji":"๐Ÿ’‡๐Ÿผโ€โ™€๏ธ","version":4},{"tone":3,"emoji":"๐Ÿ’‡๐Ÿฝโ€โ™€๏ธ","version":4},{"tone":4,"emoji":"๐Ÿ’‡๐Ÿพโ€โ™€๏ธ","version":4},{"tone":5,"emoji":"๐Ÿ’‡๐Ÿฟโ€โ™€๏ธ","version":4}]},{"emoji":"๐Ÿšถ","group":1,"order":1949,"tags":["amble","gait","hike","man","pace","pedestrian","person","stride","stroll","walk","walking"],"version":0.6,"annotation":"person walking","shortcodes":["person_walking","walking"],"skins":[{"tone":1,"emoji":"๐Ÿšถ๐Ÿป","version":1},{"tone":2,"emoji":"๐Ÿšถ๐Ÿผ","version":1},{"tone":3,"emoji":"๐Ÿšถ๐Ÿฝ","version":1},{"tone":4,"emoji":"๐Ÿšถ๐Ÿพ","version":1},{"tone":5,"emoji":"๐Ÿšถ๐Ÿฟ","version":1}]},{"emoji":"๐Ÿšถโ€โ™‚๏ธ","group":1,"order":1955,"tags":["amble","gait","hike","man","pace","pedestrian","stride","stroll","walk","walking"],"version":4,"annotation":"man walking","shortcodes":["man_walking"],"skins":[{"tone":1,"emoji":"๐Ÿšถ๐Ÿปโ€โ™‚๏ธ","version":4},{"tone":2,"emoji":"๐Ÿšถ๐Ÿผโ€โ™‚๏ธ","version":4},{"tone":3,"emoji":"๐Ÿšถ๐Ÿฝโ€โ™‚๏ธ","version":4},{"tone":4,"emoji":"๐Ÿšถ๐Ÿพโ€โ™‚๏ธ","version":4},{"tone":5,"emoji":"๐Ÿšถ๐Ÿฟโ€โ™‚๏ธ","version":4}]},{"emoji":"๐Ÿšถโ€โ™€๏ธ","group":1,"order":1967,"tags":["amble","gait","hike","man","pace","pedestrian","stride","stroll","walk","walking","woman"],"version":4,"annotation":"woman walking","shortcodes":["woman_walking"],"skins":[{"tone":1,"emoji":"๐Ÿšถ๐Ÿปโ€โ™€๏ธ","version":4},{"tone":2,"emoji":"๐Ÿšถ๐Ÿผโ€โ™€๏ธ","version":4},{"tone":3,"emoji":"๐Ÿšถ๐Ÿฝโ€โ™€๏ธ","version":4},{"tone":4,"emoji":"๐Ÿšถ๐Ÿพโ€โ™€๏ธ","version":4},{"tone":5,"emoji":"๐Ÿšถ๐Ÿฟโ€โ™€๏ธ","version":4}]},{"emoji":"๐Ÿšถโ€โžก๏ธ","group":1,"order":1979,"tags":["amble","gait","hike","man","pace","pedestrian","person","stride","stroll","walk","walking"],"version":15.1,"annotation":"person walking facing right","shortcodes":["person_walking_right"],"skins":[{"tone":1,"emoji":"๐Ÿšถ๐Ÿปโ€โžก๏ธ","version":15.1},{"tone":2,"emoji":"๐Ÿšถ๐Ÿผโ€โžก๏ธ","version":15.1},{"tone":3,"emoji":"๐Ÿšถ๐Ÿฝโ€โžก๏ธ","version":15.1},{"tone":4,"emoji":"๐Ÿšถ๐Ÿพโ€โžก๏ธ","version":15.1},{"tone":5,"emoji":"๐Ÿšถ๐Ÿฟโ€โžก๏ธ","version":15.1}]},{"emoji":"๐Ÿšถโ€โ™€๏ธโ€โžก๏ธ","group":1,"order":1991,"tags":["amble","gait","hike","man","pace","pedestrian","stride","stroll","walk","walking","woman"],"version":15.1,"annotation":"woman walking facing right","shortcodes":["woman_walking_right"],"skins":[{"tone":1,"emoji":"๐Ÿšถ๐Ÿปโ€โ™€๏ธโ€โžก๏ธ","version":15.1},{"tone":2,"emoji":"๐Ÿšถ๐Ÿผโ€โ™€๏ธโ€โžก๏ธ","version":15.1},{"tone":3,"emoji":"๐Ÿšถ๐Ÿฝโ€โ™€๏ธโ€โžก๏ธ","version":15.1},{"tone":4,"emoji":"๐Ÿšถ๐Ÿพโ€โ™€๏ธโ€โžก๏ธ","version":15.1},{"tone":5,"emoji":"๐Ÿšถ๐Ÿฟโ€โ™€๏ธโ€โžก๏ธ","version":15.1}]},{"emoji":"๐Ÿšถโ€โ™‚๏ธโ€โžก๏ธ","group":1,"order":2015,"tags":["amble","gait","hike","man","pace","pedestrian","stride","stroll","walk","walking"],"version":15.1,"annotation":"man walking facing right","shortcodes":["man_walking_right"],"skins":[{"tone":1,"emoji":"๐Ÿšถ๐Ÿปโ€โ™‚๏ธโ€โžก๏ธ","version":15.1},{"tone":2,"emoji":"๐Ÿšถ๐Ÿผโ€โ™‚๏ธโ€โžก๏ธ","version":15.1},{"tone":3,"emoji":"๐Ÿšถ๐Ÿฝโ€โ™‚๏ธโ€โžก๏ธ","version":15.1},{"tone":4,"emoji":"๐Ÿšถ๐Ÿพโ€โ™‚๏ธโ€โžก๏ธ","version":15.1},{"tone":5,"emoji":"๐Ÿšถ๐Ÿฟโ€โ™‚๏ธโ€โžก๏ธ","version":15.1}]},{"emoji":"๐Ÿง","group":1,"order":2039,"tags":["person","stand","standing"],"version":12,"annotation":"person standing","shortcodes":["person_standing","standing"],"skins":[{"tone":1,"emoji":"๐Ÿง๐Ÿป","version":12},{"tone":2,"emoji":"๐Ÿง๐Ÿผ","version":12},{"tone":3,"emoji":"๐Ÿง๐Ÿฝ","version":12},{"tone":4,"emoji":"๐Ÿง๐Ÿพ","version":12},{"tone":5,"emoji":"๐Ÿง๐Ÿฟ","version":12}]},{"emoji":"๐Ÿงโ€โ™‚๏ธ","group":1,"order":2045,"tags":["man","stand","standing"],"version":12,"annotation":"man standing","shortcodes":["man_standing"],"skins":[{"tone":1,"emoji":"๐Ÿง๐Ÿปโ€โ™‚๏ธ","version":12},{"tone":2,"emoji":"๐Ÿง๐Ÿผโ€โ™‚๏ธ","version":12},{"tone":3,"emoji":"๐Ÿง๐Ÿฝโ€โ™‚๏ธ","version":12},{"tone":4,"emoji":"๐Ÿง๐Ÿพโ€โ™‚๏ธ","version":12},{"tone":5,"emoji":"๐Ÿง๐Ÿฟโ€โ™‚๏ธ","version":12}]},{"emoji":"๐Ÿงโ€โ™€๏ธ","group":1,"order":2057,"tags":["stand","standing","woman"],"version":12,"annotation":"woman standing","shortcodes":["woman_standing"],"skins":[{"tone":1,"emoji":"๐Ÿง๐Ÿปโ€โ™€๏ธ","version":12},{"tone":2,"emoji":"๐Ÿง๐Ÿผโ€โ™€๏ธ","version":12},{"tone":3,"emoji":"๐Ÿง๐Ÿฝโ€โ™€๏ธ","version":12},{"tone":4,"emoji":"๐Ÿง๐Ÿพโ€โ™€๏ธ","version":12},{"tone":5,"emoji":"๐Ÿง๐Ÿฟโ€โ™€๏ธ","version":12}]},{"emoji":"๐ŸงŽ","group":1,"order":2069,"tags":["kneel","kneeling","knees","person"],"version":12,"annotation":"person kneeling","shortcodes":["kneeling","person_kneeling"],"skins":[{"tone":1,"emoji":"๐ŸงŽ๐Ÿป","version":12},{"tone":2,"emoji":"๐ŸงŽ๐Ÿผ","version":12},{"tone":3,"emoji":"๐ŸงŽ๐Ÿฝ","version":12},{"tone":4,"emoji":"๐ŸงŽ๐Ÿพ","version":12},{"tone":5,"emoji":"๐ŸงŽ๐Ÿฟ","version":12}]},{"emoji":"๐ŸงŽโ€โ™‚๏ธ","group":1,"order":2075,"tags":["kneel","kneeling","knees","man"],"version":12,"annotation":"man kneeling","shortcodes":["man_kneeling"],"skins":[{"tone":1,"emoji":"๐ŸงŽ๐Ÿปโ€โ™‚๏ธ","version":12},{"tone":2,"emoji":"๐ŸงŽ๐Ÿผโ€โ™‚๏ธ","version":12},{"tone":3,"emoji":"๐ŸงŽ๐Ÿฝโ€โ™‚๏ธ","version":12},{"tone":4,"emoji":"๐ŸงŽ๐Ÿพโ€โ™‚๏ธ","version":12},{"tone":5,"emoji":"๐ŸงŽ๐Ÿฟโ€โ™‚๏ธ","version":12}]},{"emoji":"๐ŸงŽโ€โ™€๏ธ","group":1,"order":2087,"tags":["kneel","kneeling","knees","woman"],"version":12,"annotation":"woman kneeling","shortcodes":["woman_kneeling"],"skins":[{"tone":1,"emoji":"๐ŸงŽ๐Ÿปโ€โ™€๏ธ","version":12},{"tone":2,"emoji":"๐ŸงŽ๐Ÿผโ€โ™€๏ธ","version":12},{"tone":3,"emoji":"๐ŸงŽ๐Ÿฝโ€โ™€๏ธ","version":12},{"tone":4,"emoji":"๐ŸงŽ๐Ÿพโ€โ™€๏ธ","version":12},{"tone":5,"emoji":"๐ŸงŽ๐Ÿฟโ€โ™€๏ธ","version":12}]},{"emoji":"๐ŸงŽโ€โžก๏ธ","group":1,"order":2099,"tags":["kneel","kneeling","knees","person"],"version":15.1,"annotation":"person kneeling facing right","shortcodes":["person_kneeling_right"],"skins":[{"tone":1,"emoji":"๐ŸงŽ๐Ÿปโ€โžก๏ธ","version":15.1},{"tone":2,"emoji":"๐ŸงŽ๐Ÿผโ€โžก๏ธ","version":15.1},{"tone":3,"emoji":"๐ŸงŽ๐Ÿฝโ€โžก๏ธ","version":15.1},{"tone":4,"emoji":"๐ŸงŽ๐Ÿพโ€โžก๏ธ","version":15.1},{"tone":5,"emoji":"๐ŸงŽ๐Ÿฟโ€โžก๏ธ","version":15.1}]},{"emoji":"๐ŸงŽโ€โ™€๏ธโ€โžก๏ธ","group":1,"order":2111,"tags":["kneel","kneeling","knees","woman"],"version":15.1,"annotation":"woman kneeling facing right","shortcodes":["woman_kneeling_right"],"skins":[{"tone":1,"emoji":"๐ŸงŽ๐Ÿปโ€โ™€๏ธโ€โžก๏ธ","version":15.1},{"tone":2,"emoji":"๐ŸงŽ๐Ÿผโ€โ™€๏ธโ€โžก๏ธ","version":15.1},{"tone":3,"emoji":"๐ŸงŽ๐Ÿฝโ€โ™€๏ธโ€โžก๏ธ","version":15.1},{"tone":4,"emoji":"๐ŸงŽ๐Ÿพโ€โ™€๏ธโ€โžก๏ธ","version":15.1},{"tone":5,"emoji":"๐ŸงŽ๐Ÿฟโ€โ™€๏ธโ€โžก๏ธ","version":15.1}]},{"emoji":"๐ŸงŽโ€โ™‚๏ธโ€โžก๏ธ","group":1,"order":2135,"tags":["kneel","kneeling","knees","man"],"version":15.1,"annotation":"man kneeling facing right","shortcodes":["man_kneeling_right"],"skins":[{"tone":1,"emoji":"๐ŸงŽ๐Ÿปโ€โ™‚๏ธโ€โžก๏ธ","version":15.1},{"tone":2,"emoji":"๐ŸงŽ๐Ÿผโ€โ™‚๏ธโ€โžก๏ธ","version":15.1},{"tone":3,"emoji":"๐ŸงŽ๐Ÿฝโ€โ™‚๏ธโ€โžก๏ธ","version":15.1},{"tone":4,"emoji":"๐ŸงŽ๐Ÿพโ€โ™‚๏ธโ€โžก๏ธ","version":15.1},{"tone":5,"emoji":"๐ŸงŽ๐Ÿฟโ€โ™‚๏ธโ€โžก๏ธ","version":15.1}]},{"emoji":"๐Ÿง‘โ€๐Ÿฆฏ","group":1,"order":2159,"tags":["accessibility","blind","cane","person","probing","white"],"version":12.1,"annotation":"person with white cane","shortcodes":["person_with_probing_cane","person_with_white_cane"],"skins":[{"tone":1,"emoji":"๐Ÿง‘๐Ÿปโ€๐Ÿฆฏ","version":12.1},{"tone":2,"emoji":"๐Ÿง‘๐Ÿผโ€๐Ÿฆฏ","version":12.1},{"tone":3,"emoji":"๐Ÿง‘๐Ÿฝโ€๐Ÿฆฏ","version":12.1},{"tone":4,"emoji":"๐Ÿง‘๐Ÿพโ€๐Ÿฆฏ","version":12.1},{"tone":5,"emoji":"๐Ÿง‘๐Ÿฟโ€๐Ÿฆฏ","version":12.1}]},{"emoji":"๐Ÿง‘โ€๐Ÿฆฏโ€โžก๏ธ","group":1,"order":2165,"tags":["accessibility","blind","cane","person","probing","white"],"version":15.1,"annotation":"person with white cane facing right","shortcodes":["person_with_white_cane_right"],"skins":[{"tone":1,"emoji":"๐Ÿง‘๐Ÿปโ€๐Ÿฆฏโ€โžก๏ธ","version":15.1},{"tone":2,"emoji":"๐Ÿง‘๐Ÿผโ€๐Ÿฆฏโ€โžก๏ธ","version":15.1},{"tone":3,"emoji":"๐Ÿง‘๐Ÿฝโ€๐Ÿฆฏโ€โžก๏ธ","version":15.1},{"tone":4,"emoji":"๐Ÿง‘๐Ÿพโ€๐Ÿฆฏโ€โžก๏ธ","version":15.1},{"tone":5,"emoji":"๐Ÿง‘๐Ÿฟโ€๐Ÿฆฏโ€โžก๏ธ","version":15.1}]},{"emoji":"๐Ÿ‘จโ€๐Ÿฆฏ","group":1,"order":2177,"tags":["accessibility","blind","cane","man","probing","white"],"version":12,"annotation":"man with white cane","shortcodes":["man_with_probing_cane","man_with_white_cane"],"skins":[{"tone":1,"emoji":"๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฏ","version":12},{"tone":2,"emoji":"๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฏ","version":12},{"tone":3,"emoji":"๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฏ","version":12},{"tone":4,"emoji":"๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฏ","version":12},{"tone":5,"emoji":"๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฏ","version":12}]},{"emoji":"๐Ÿ‘จโ€๐Ÿฆฏโ€โžก๏ธ","group":1,"order":2183,"tags":["accessibility","blind","cane","man","probing","white"],"version":15.1,"annotation":"man with white cane facing right","shortcodes":["man_with_white_cane_right"],"skins":[{"tone":1,"emoji":"๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฏโ€โžก๏ธ","version":15.1},{"tone":2,"emoji":"๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฏโ€โžก๏ธ","version":15.1},{"tone":3,"emoji":"๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฏโ€โžก๏ธ","version":15.1},{"tone":4,"emoji":"๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฏโ€โžก๏ธ","version":15.1},{"tone":5,"emoji":"๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฏโ€โžก๏ธ","version":15.1}]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿฆฏ","group":1,"order":2195,"tags":["accessibility","blind","cane","probing","white","woman"],"version":12,"annotation":"woman with white cane","shortcodes":["woman_with_probing_cane","woman_with_white_cane"],"skins":[{"tone":1,"emoji":"๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฏ","version":12},{"tone":2,"emoji":"๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฏ","version":12},{"tone":3,"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฏ","version":12},{"tone":4,"emoji":"๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฏ","version":12},{"tone":5,"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฏ","version":12}]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿฆฏโ€โžก๏ธ","group":1,"order":2201,"tags":["accessibility","blind","cane","probing","white","woman"],"version":15.1,"annotation":"woman with white cane facing right","shortcodes":["woman_with_white_cane_right"],"skins":[{"tone":1,"emoji":"๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฏโ€โžก๏ธ","version":15.1},{"tone":2,"emoji":"๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฏโ€โžก๏ธ","version":15.1},{"tone":3,"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฏโ€โžก๏ธ","version":15.1},{"tone":4,"emoji":"๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฏโ€โžก๏ธ","version":15.1},{"tone":5,"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฏโ€โžก๏ธ","version":15.1}]},{"emoji":"๐Ÿง‘โ€๐Ÿฆผ","group":1,"order":2213,"tags":["accessibility","motorized","person","wheelchair"],"version":12.1,"annotation":"person in motorized wheelchair","shortcodes":["person_in_motorized_wheelchair"],"skins":[{"tone":1,"emoji":"๐Ÿง‘๐Ÿปโ€๐Ÿฆผ","version":12.1},{"tone":2,"emoji":"๐Ÿง‘๐Ÿผโ€๐Ÿฆผ","version":12.1},{"tone":3,"emoji":"๐Ÿง‘๐Ÿฝโ€๐Ÿฆผ","version":12.1},{"tone":4,"emoji":"๐Ÿง‘๐Ÿพโ€๐Ÿฆผ","version":12.1},{"tone":5,"emoji":"๐Ÿง‘๐Ÿฟโ€๐Ÿฆผ","version":12.1}]},{"emoji":"๐Ÿง‘โ€๐Ÿฆผโ€โžก๏ธ","group":1,"order":2219,"tags":["accessibility","motorized","person","wheelchair"],"version":15.1,"annotation":"person in motorized wheelchair facing right","shortcodes":["person_in_motorized_wheelchair_right"],"skins":[{"tone":1,"emoji":"๐Ÿง‘๐Ÿปโ€๐Ÿฆผโ€โžก๏ธ","version":15.1},{"tone":2,"emoji":"๐Ÿง‘๐Ÿผโ€๐Ÿฆผโ€โžก๏ธ","version":15.1},{"tone":3,"emoji":"๐Ÿง‘๐Ÿฝโ€๐Ÿฆผโ€โžก๏ธ","version":15.1},{"tone":4,"emoji":"๐Ÿง‘๐Ÿพโ€๐Ÿฆผโ€โžก๏ธ","version":15.1},{"tone":5,"emoji":"๐Ÿง‘๐Ÿฟโ€๐Ÿฆผโ€โžก๏ธ","version":15.1}]},{"emoji":"๐Ÿ‘จโ€๐Ÿฆผ","group":1,"order":2231,"tags":["accessibility","man","motorized","wheelchair"],"version":12,"annotation":"man in motorized wheelchair","shortcodes":["man_in_motorized_wheelchair"],"skins":[{"tone":1,"emoji":"๐Ÿ‘จ๐Ÿปโ€๐Ÿฆผ","version":12},{"tone":2,"emoji":"๐Ÿ‘จ๐Ÿผโ€๐Ÿฆผ","version":12},{"tone":3,"emoji":"๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆผ","version":12},{"tone":4,"emoji":"๐Ÿ‘จ๐Ÿพโ€๐Ÿฆผ","version":12},{"tone":5,"emoji":"๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆผ","version":12}]},{"emoji":"๐Ÿ‘จโ€๐Ÿฆผโ€โžก๏ธ","group":1,"order":2237,"tags":["accessibility","man","motorized","wheelchair"],"version":15.1,"annotation":"man in motorized wheelchair facing right","shortcodes":["man_in_motorized_wheelchair_right"],"skins":[{"tone":1,"emoji":"๐Ÿ‘จ๐Ÿปโ€๐Ÿฆผโ€โžก๏ธ","version":15.1},{"tone":2,"emoji":"๐Ÿ‘จ๐Ÿผโ€๐Ÿฆผโ€โžก๏ธ","version":15.1},{"tone":3,"emoji":"๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆผโ€โžก๏ธ","version":15.1},{"tone":4,"emoji":"๐Ÿ‘จ๐Ÿพโ€๐Ÿฆผโ€โžก๏ธ","version":15.1},{"tone":5,"emoji":"๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆผโ€โžก๏ธ","version":15.1}]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿฆผ","group":1,"order":2249,"tags":["accessibility","motorized","wheelchair","woman"],"version":12,"annotation":"woman in motorized wheelchair","shortcodes":["woman_in_motorized_wheelchair"],"skins":[{"tone":1,"emoji":"๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆผ","version":12},{"tone":2,"emoji":"๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆผ","version":12},{"tone":3,"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆผ","version":12},{"tone":4,"emoji":"๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆผ","version":12},{"tone":5,"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆผ","version":12}]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿฆผโ€โžก๏ธ","group":1,"order":2255,"tags":["accessibility","motorized","wheelchair","woman"],"version":15.1,"annotation":"woman in motorized wheelchair facing right","shortcodes":["woman_in_motorized_wheelchair_right"],"skins":[{"tone":1,"emoji":"๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆผโ€โžก๏ธ","version":15.1},{"tone":2,"emoji":"๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆผโ€โžก๏ธ","version":15.1},{"tone":3,"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆผโ€โžก๏ธ","version":15.1},{"tone":4,"emoji":"๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆผโ€โžก๏ธ","version":15.1},{"tone":5,"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆผโ€โžก๏ธ","version":15.1}]},{"emoji":"๐Ÿง‘โ€๐Ÿฆฝ","group":1,"order":2267,"tags":["accessibility","manual","person","wheelchair"],"version":12.1,"annotation":"person in manual wheelchair","shortcodes":["person_in_manual_wheelchair"],"skins":[{"tone":1,"emoji":"๐Ÿง‘๐Ÿปโ€๐Ÿฆฝ","version":12.1},{"tone":2,"emoji":"๐Ÿง‘๐Ÿผโ€๐Ÿฆฝ","version":12.1},{"tone":3,"emoji":"๐Ÿง‘๐Ÿฝโ€๐Ÿฆฝ","version":12.1},{"tone":4,"emoji":"๐Ÿง‘๐Ÿพโ€๐Ÿฆฝ","version":12.1},{"tone":5,"emoji":"๐Ÿง‘๐Ÿฟโ€๐Ÿฆฝ","version":12.1}]},{"emoji":"๐Ÿง‘โ€๐Ÿฆฝโ€โžก๏ธ","group":1,"order":2273,"tags":["accessibility","manual","person","wheelchair"],"version":15.1,"annotation":"person in manual wheelchair facing right","shortcodes":["person_in_manual_wheelchair_right"],"skins":[{"tone":1,"emoji":"๐Ÿง‘๐Ÿปโ€๐Ÿฆฝโ€โžก๏ธ","version":15.1},{"tone":2,"emoji":"๐Ÿง‘๐Ÿผโ€๐Ÿฆฝโ€โžก๏ธ","version":15.1},{"tone":3,"emoji":"๐Ÿง‘๐Ÿฝโ€๐Ÿฆฝโ€โžก๏ธ","version":15.1},{"tone":4,"emoji":"๐Ÿง‘๐Ÿพโ€๐Ÿฆฝโ€โžก๏ธ","version":15.1},{"tone":5,"emoji":"๐Ÿง‘๐Ÿฟโ€๐Ÿฆฝโ€โžก๏ธ","version":15.1}]},{"emoji":"๐Ÿ‘จโ€๐Ÿฆฝ","group":1,"order":2285,"tags":["accessibility","man","manual","wheelchair"],"version":12,"annotation":"man in manual wheelchair","shortcodes":["man_in_manual_wheelchair"],"skins":[{"tone":1,"emoji":"๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฝ","version":12},{"tone":2,"emoji":"๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฝ","version":12},{"tone":3,"emoji":"๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฝ","version":12},{"tone":4,"emoji":"๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฝ","version":12},{"tone":5,"emoji":"๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฝ","version":12}]},{"emoji":"๐Ÿ‘จโ€๐Ÿฆฝโ€โžก๏ธ","group":1,"order":2291,"tags":["accessibility","man","manual","wheelchair"],"version":15.1,"annotation":"man in manual wheelchair facing right","shortcodes":["man_in_manual_wheelchair_right"],"skins":[{"tone":1,"emoji":"๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฝโ€โžก๏ธ","version":15.1},{"tone":2,"emoji":"๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฝโ€โžก๏ธ","version":15.1},{"tone":3,"emoji":"๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฝโ€โžก๏ธ","version":15.1},{"tone":4,"emoji":"๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฝโ€โžก๏ธ","version":15.1},{"tone":5,"emoji":"๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฝโ€โžก๏ธ","version":15.1}]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿฆฝ","group":1,"order":2303,"tags":["accessibility","manual","wheelchair","woman"],"version":12,"annotation":"woman in manual wheelchair","shortcodes":["woman_in_manual_wheelchair"],"skins":[{"tone":1,"emoji":"๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฝ","version":12},{"tone":2,"emoji":"๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฝ","version":12},{"tone":3,"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฝ","version":12},{"tone":4,"emoji":"๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฝ","version":12},{"tone":5,"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฝ","version":12}]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿฆฝโ€โžก๏ธ","group":1,"order":2309,"tags":["accessibility","manual","wheelchair","woman"],"version":15.1,"annotation":"woman in manual wheelchair facing right","shortcodes":["woman_in_manual_wheelchair_right"],"skins":[{"tone":1,"emoji":"๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฝโ€โžก๏ธ","version":15.1},{"tone":2,"emoji":"๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฝโ€โžก๏ธ","version":15.1},{"tone":3,"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฝโ€โžก๏ธ","version":15.1},{"tone":4,"emoji":"๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฝโ€โžก๏ธ","version":15.1},{"tone":5,"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฝโ€โžก๏ธ","version":15.1}]},{"emoji":"๐Ÿƒ","group":1,"order":2321,"tags":["fast","hurry","marathon","move","person","quick","race","racing","run","rush","speed"],"version":0.6,"annotation":"person running","shortcodes":["person_running","running"],"skins":[{"tone":1,"emoji":"๐Ÿƒ๐Ÿป","version":1},{"tone":2,"emoji":"๐Ÿƒ๐Ÿผ","version":1},{"tone":3,"emoji":"๐Ÿƒ๐Ÿฝ","version":1},{"tone":4,"emoji":"๐Ÿƒ๐Ÿพ","version":1},{"tone":5,"emoji":"๐Ÿƒ๐Ÿฟ","version":1}]},{"emoji":"๐Ÿƒโ€โ™‚๏ธ","group":1,"order":2327,"tags":["fast","hurry","man","marathon","move","quick","race","racing","run","rush","speed"],"version":4,"annotation":"man running","shortcodes":["man_running"],"skins":[{"tone":1,"emoji":"๐Ÿƒ๐Ÿปโ€โ™‚๏ธ","version":4},{"tone":2,"emoji":"๐Ÿƒ๐Ÿผโ€โ™‚๏ธ","version":4},{"tone":3,"emoji":"๐Ÿƒ๐Ÿฝโ€โ™‚๏ธ","version":4},{"tone":4,"emoji":"๐Ÿƒ๐Ÿพโ€โ™‚๏ธ","version":4},{"tone":5,"emoji":"๐Ÿƒ๐Ÿฟโ€โ™‚๏ธ","version":4}]},{"emoji":"๐Ÿƒโ€โ™€๏ธ","group":1,"order":2339,"tags":["fast","hurry","marathon","move","quick","race","racing","run","rush","speed","woman"],"version":4,"annotation":"woman running","shortcodes":["woman_running"],"skins":[{"tone":1,"emoji":"๐Ÿƒ๐Ÿปโ€โ™€๏ธ","version":4},{"tone":2,"emoji":"๐Ÿƒ๐Ÿผโ€โ™€๏ธ","version":4},{"tone":3,"emoji":"๐Ÿƒ๐Ÿฝโ€โ™€๏ธ","version":4},{"tone":4,"emoji":"๐Ÿƒ๐Ÿพโ€โ™€๏ธ","version":4},{"tone":5,"emoji":"๐Ÿƒ๐Ÿฟโ€โ™€๏ธ","version":4}]},{"emoji":"๐Ÿƒโ€โžก๏ธ","group":1,"order":2351,"tags":["fast","hurry","marathon","move","person","quick","race","racing","run","rush","speed"],"version":15.1,"annotation":"person running facing right","shortcodes":["person_running_right"],"skins":[{"tone":1,"emoji":"๐Ÿƒ๐Ÿปโ€โžก๏ธ","version":15.1},{"tone":2,"emoji":"๐Ÿƒ๐Ÿผโ€โžก๏ธ","version":15.1},{"tone":3,"emoji":"๐Ÿƒ๐Ÿฝโ€โžก๏ธ","version":15.1},{"tone":4,"emoji":"๐Ÿƒ๐Ÿพโ€โžก๏ธ","version":15.1},{"tone":5,"emoji":"๐Ÿƒ๐Ÿฟโ€โžก๏ธ","version":15.1}]},{"emoji":"๐Ÿƒโ€โ™€๏ธโ€โžก๏ธ","group":1,"order":2363,"tags":["fast","hurry","marathon","move","quick","race","racing","run","rush","speed","woman"],"version":15.1,"annotation":"woman running facing right","shortcodes":["woman_running_right"],"skins":[{"tone":1,"emoji":"๐Ÿƒ๐Ÿปโ€โ™€๏ธโ€โžก๏ธ","version":15.1},{"tone":2,"emoji":"๐Ÿƒ๐Ÿผโ€โ™€๏ธโ€โžก๏ธ","version":15.1},{"tone":3,"emoji":"๐Ÿƒ๐Ÿฝโ€โ™€๏ธโ€โžก๏ธ","version":15.1},{"tone":4,"emoji":"๐Ÿƒ๐Ÿพโ€โ™€๏ธโ€โžก๏ธ","version":15.1},{"tone":5,"emoji":"๐Ÿƒ๐Ÿฟโ€โ™€๏ธโ€โžก๏ธ","version":15.1}]},{"emoji":"๐Ÿƒโ€โ™‚๏ธโ€โžก๏ธ","group":1,"order":2387,"tags":["fast","hurry","man","marathon","move","quick","race","racing","run","rush","speed"],"version":15.1,"annotation":"man running facing right","shortcodes":["man_running_right"],"skins":[{"tone":1,"emoji":"๐Ÿƒ๐Ÿปโ€โ™‚๏ธโ€โžก๏ธ","version":15.1},{"tone":2,"emoji":"๐Ÿƒ๐Ÿผโ€โ™‚๏ธโ€โžก๏ธ","version":15.1},{"tone":3,"emoji":"๐Ÿƒ๐Ÿฝโ€โ™‚๏ธโ€โžก๏ธ","version":15.1},{"tone":4,"emoji":"๐Ÿƒ๐Ÿพโ€โ™‚๏ธโ€โžก๏ธ","version":15.1},{"tone":5,"emoji":"๐Ÿƒ๐Ÿฟโ€โ™‚๏ธโ€โžก๏ธ","version":15.1}]},{"emoji":"๐Ÿ’ƒ","group":1,"order":2411,"tags":["dance","dancer","dancing","elegant","festive","flair","flamenco","groove","letโ€™s","salsa","tango","woman"],"version":0.6,"annotation":"woman dancing","shortcodes":["dancer","woman_dancing"],"skins":[{"tone":1,"emoji":"๐Ÿ’ƒ๐Ÿป","version":1},{"tone":2,"emoji":"๐Ÿ’ƒ๐Ÿผ","version":1},{"tone":3,"emoji":"๐Ÿ’ƒ๐Ÿฝ","version":1},{"tone":4,"emoji":"๐Ÿ’ƒ๐Ÿพ","version":1},{"tone":5,"emoji":"๐Ÿ’ƒ๐Ÿฟ","version":1}]},{"emoji":"๐Ÿ•บ","group":1,"order":2417,"tags":["dance","dancer","dancing","elegant","festive","flair","flamenco","groove","letโ€™s","man","salsa","tango"],"version":3,"annotation":"man dancing","shortcodes":["man_dancing"],"skins":[{"tone":1,"emoji":"๐Ÿ•บ๐Ÿป","version":3},{"tone":2,"emoji":"๐Ÿ•บ๐Ÿผ","version":3},{"tone":3,"emoji":"๐Ÿ•บ๐Ÿฝ","version":3},{"tone":4,"emoji":"๐Ÿ•บ๐Ÿพ","version":3},{"tone":5,"emoji":"๐Ÿ•บ๐Ÿฟ","version":3}]},{"emoji":"๐Ÿ•ด๏ธ","group":1,"order":2424,"tags":["business","levitating","person","suit"],"version":0.7,"annotation":"person in suit levitating","shortcodes":["levitate","levitating","person_in_suit_levitating"],"skins":[{"tone":1,"emoji":"๐Ÿ•ด๐Ÿป","version":4},{"tone":2,"emoji":"๐Ÿ•ด๐Ÿผ","version":4},{"tone":3,"emoji":"๐Ÿ•ด๐Ÿฝ","version":4},{"tone":4,"emoji":"๐Ÿ•ด๐Ÿพ","version":4},{"tone":5,"emoji":"๐Ÿ•ด๐Ÿฟ","version":4}]},{"emoji":"๐Ÿ‘ฏ","group":1,"order":2430,"tags":["bestie","bff","bunny","counterpart","dancer","double","ear","identical","pair","party","partying","people","soulmate","twin","twinsies"],"version":0.6,"annotation":"people with bunny ears","shortcodes":["dancers","people_with_bunny_ears_partying"]},{"emoji":"๐Ÿ‘ฏโ€โ™‚๏ธ","group":1,"order":2431,"tags":["bestie","bff","bunny","counterpart","dancer","double","ear","identical","men","pair","party","partying","people","soulmate","twin","twinsies"],"version":4,"annotation":"men with bunny ears","shortcodes":["men_with_bunny_ears_partying"]},{"emoji":"๐Ÿ‘ฏโ€โ™€๏ธ","group":1,"order":2433,"tags":["bestie","bff","bunny","counterpart","dancer","double","ear","identical","pair","party","partying","people","soulmate","twin","twinsies","women"],"version":4,"annotation":"women with bunny ears","shortcodes":["women_with_bunny_ears_partying"]},{"emoji":"๐Ÿง–","group":1,"order":2435,"tags":["day","luxurious","pamper","person","relax","room","sauna","spa","steam","steambath","unwind"],"version":5,"annotation":"person in steamy room","shortcodes":["person_in_steamy_room"],"skins":[{"tone":1,"emoji":"๐Ÿง–๐Ÿป","version":5},{"tone":2,"emoji":"๐Ÿง–๐Ÿผ","version":5},{"tone":3,"emoji":"๐Ÿง–๐Ÿฝ","version":5},{"tone":4,"emoji":"๐Ÿง–๐Ÿพ","version":5},{"tone":5,"emoji":"๐Ÿง–๐Ÿฟ","version":5}]},{"emoji":"๐Ÿง–โ€โ™‚๏ธ","group":1,"order":2441,"tags":["day","luxurious","man","pamper","relax","room","sauna","spa","steam","steambath","unwind"],"version":5,"annotation":"man in steamy room","shortcodes":["man_in_steamy_room"],"skins":[{"tone":1,"emoji":"๐Ÿง–๐Ÿปโ€โ™‚๏ธ","version":5},{"tone":2,"emoji":"๐Ÿง–๐Ÿผโ€โ™‚๏ธ","version":5},{"tone":3,"emoji":"๐Ÿง–๐Ÿฝโ€โ™‚๏ธ","version":5},{"tone":4,"emoji":"๐Ÿง–๐Ÿพโ€โ™‚๏ธ","version":5},{"tone":5,"emoji":"๐Ÿง–๐Ÿฟโ€โ™‚๏ธ","version":5}]},{"emoji":"๐Ÿง–โ€โ™€๏ธ","group":1,"order":2453,"tags":["day","luxurious","pamper","relax","room","sauna","spa","steam","steambath","unwind","woman"],"version":5,"annotation":"woman in steamy room","shortcodes":["woman_in_steamy_room"],"skins":[{"tone":1,"emoji":"๐Ÿง–๐Ÿปโ€โ™€๏ธ","version":5},{"tone":2,"emoji":"๐Ÿง–๐Ÿผโ€โ™€๏ธ","version":5},{"tone":3,"emoji":"๐Ÿง–๐Ÿฝโ€โ™€๏ธ","version":5},{"tone":4,"emoji":"๐Ÿง–๐Ÿพโ€โ™€๏ธ","version":5},{"tone":5,"emoji":"๐Ÿง–๐Ÿฟโ€โ™€๏ธ","version":5}]},{"emoji":"๐Ÿง—","group":1,"order":2465,"tags":["climb","climber","climbing","mountain","person","rock","scale","up"],"version":5,"annotation":"person climbing","shortcodes":["climbing","person_climbing"],"skins":[{"tone":1,"emoji":"๐Ÿง—๐Ÿป","version":5},{"tone":2,"emoji":"๐Ÿง—๐Ÿผ","version":5},{"tone":3,"emoji":"๐Ÿง—๐Ÿฝ","version":5},{"tone":4,"emoji":"๐Ÿง—๐Ÿพ","version":5},{"tone":5,"emoji":"๐Ÿง—๐Ÿฟ","version":5}]},{"emoji":"๐Ÿง—โ€โ™‚๏ธ","group":1,"order":2471,"tags":["climb","climber","climbing","man","mountain","rock","scale","up"],"version":5,"annotation":"man climbing","shortcodes":["man_climbing"],"skins":[{"tone":1,"emoji":"๐Ÿง—๐Ÿปโ€โ™‚๏ธ","version":5},{"tone":2,"emoji":"๐Ÿง—๐Ÿผโ€โ™‚๏ธ","version":5},{"tone":3,"emoji":"๐Ÿง—๐Ÿฝโ€โ™‚๏ธ","version":5},{"tone":4,"emoji":"๐Ÿง—๐Ÿพโ€โ™‚๏ธ","version":5},{"tone":5,"emoji":"๐Ÿง—๐Ÿฟโ€โ™‚๏ธ","version":5}]},{"emoji":"๐Ÿง—โ€โ™€๏ธ","group":1,"order":2483,"tags":["climb","climber","climbing","mountain","rock","scale","up","woman"],"version":5,"annotation":"woman climbing","shortcodes":["woman_climbing"],"skins":[{"tone":1,"emoji":"๐Ÿง—๐Ÿปโ€โ™€๏ธ","version":5},{"tone":2,"emoji":"๐Ÿง—๐Ÿผโ€โ™€๏ธ","version":5},{"tone":3,"emoji":"๐Ÿง—๐Ÿฝโ€โ™€๏ธ","version":5},{"tone":4,"emoji":"๐Ÿง—๐Ÿพโ€โ™€๏ธ","version":5},{"tone":5,"emoji":"๐Ÿง—๐Ÿฟโ€โ™€๏ธ","version":5}]},{"emoji":"๐Ÿคบ","group":1,"order":2495,"tags":["fencer","fencing","person","sword"],"version":3,"annotation":"person fencing","shortcodes":["fencer","fencing","person_fencing"]},{"emoji":"๐Ÿ‡","group":1,"order":2496,"tags":["horse","jockey","racehorse","racing","riding","sport"],"version":1,"annotation":"horse racing","shortcodes":["horse_racing"],"skins":[{"tone":1,"emoji":"๐Ÿ‡๐Ÿป","version":1},{"tone":2,"emoji":"๐Ÿ‡๐Ÿผ","version":1},{"tone":3,"emoji":"๐Ÿ‡๐Ÿฝ","version":1},{"tone":4,"emoji":"๐Ÿ‡๐Ÿพ","version":1},{"tone":5,"emoji":"๐Ÿ‡๐Ÿฟ","version":1}]},{"emoji":"โ›ท๏ธ","group":1,"order":2503,"tags":["ski","snow"],"version":0.7,"annotation":"skier","shortcodes":["person_skiing","skier","skiing"]},{"emoji":"๐Ÿ‚๏ธ","group":1,"order":2504,"tags":["ski","snow","snowboard","sport"],"version":0.6,"annotation":"snowboarder","shortcodes":["person_snowboarding","snowboarder","snowboarding"],"skins":[{"tone":1,"emoji":"๐Ÿ‚๐Ÿป","version":1},{"tone":2,"emoji":"๐Ÿ‚๐Ÿผ","version":1},{"tone":3,"emoji":"๐Ÿ‚๐Ÿฝ","version":1},{"tone":4,"emoji":"๐Ÿ‚๐Ÿพ","version":1},{"tone":5,"emoji":"๐Ÿ‚๐Ÿฟ","version":1}]},{"emoji":"๐ŸŒ๏ธ","group":1,"order":2511,"tags":["ball","birdie","caddy","driving","golf","golfing","green","person","pga","putt","range","tee"],"version":0.7,"annotation":"person golfing","shortcodes":["golfer","golfing","person_golfing"],"skins":[{"tone":1,"emoji":"๐ŸŒ๐Ÿป","version":4},{"tone":2,"emoji":"๐ŸŒ๐Ÿผ","version":4},{"tone":3,"emoji":"๐ŸŒ๐Ÿฝ","version":4},{"tone":4,"emoji":"๐ŸŒ๐Ÿพ","version":4},{"tone":5,"emoji":"๐ŸŒ๐Ÿฟ","version":4}]},{"emoji":"๐ŸŒ๏ธโ€โ™‚๏ธ","group":1,"order":2517,"tags":["ball","birdie","caddy","driving","golf","golfing","green","man","pga","putt","range","tee"],"version":4,"annotation":"man golfing","shortcodes":["man_golfing"],"skins":[{"tone":1,"emoji":"๐ŸŒ๐Ÿปโ€โ™‚๏ธ","version":4},{"tone":2,"emoji":"๐ŸŒ๐Ÿผโ€โ™‚๏ธ","version":4},{"tone":3,"emoji":"๐ŸŒ๐Ÿฝโ€โ™‚๏ธ","version":4},{"tone":4,"emoji":"๐ŸŒ๐Ÿพโ€โ™‚๏ธ","version":4},{"tone":5,"emoji":"๐ŸŒ๐Ÿฟโ€โ™‚๏ธ","version":4}]},{"emoji":"๐ŸŒ๏ธโ€โ™€๏ธ","group":1,"order":2531,"tags":["ball","birdie","caddy","driving","golf","golfing","green","pga","putt","range","tee","woman"],"version":4,"annotation":"woman golfing","shortcodes":["woman_golfing"],"skins":[{"tone":1,"emoji":"๐ŸŒ๐Ÿปโ€โ™€๏ธ","version":4},{"tone":2,"emoji":"๐ŸŒ๐Ÿผโ€โ™€๏ธ","version":4},{"tone":3,"emoji":"๐ŸŒ๐Ÿฝโ€โ™€๏ธ","version":4},{"tone":4,"emoji":"๐ŸŒ๐Ÿพโ€โ™€๏ธ","version":4},{"tone":5,"emoji":"๐ŸŒ๐Ÿฟโ€โ™€๏ธ","version":4}]},{"emoji":"๐Ÿ„๏ธ","group":1,"order":2545,"tags":["beach","ocean","person","sport","surf","surfer","surfing","swell","waves"],"version":0.6,"annotation":"person surfing","shortcodes":["person_surfing","surfer","surfing"],"skins":[{"tone":1,"emoji":"๐Ÿ„๐Ÿป","version":1},{"tone":2,"emoji":"๐Ÿ„๐Ÿผ","version":1},{"tone":3,"emoji":"๐Ÿ„๐Ÿฝ","version":1},{"tone":4,"emoji":"๐Ÿ„๐Ÿพ","version":1},{"tone":5,"emoji":"๐Ÿ„๐Ÿฟ","version":1}]},{"emoji":"๐Ÿ„โ€โ™‚๏ธ","group":1,"order":2551,"tags":["beach","man","ocean","sport","surf","surfer","surfing","swell","waves"],"version":4,"annotation":"man surfing","shortcodes":["man_surfing"],"skins":[{"tone":1,"emoji":"๐Ÿ„๐Ÿปโ€โ™‚๏ธ","version":4},{"tone":2,"emoji":"๐Ÿ„๐Ÿผโ€โ™‚๏ธ","version":4},{"tone":3,"emoji":"๐Ÿ„๐Ÿฝโ€โ™‚๏ธ","version":4},{"tone":4,"emoji":"๐Ÿ„๐Ÿพโ€โ™‚๏ธ","version":4},{"tone":5,"emoji":"๐Ÿ„๐Ÿฟโ€โ™‚๏ธ","version":4}]},{"emoji":"๐Ÿ„โ€โ™€๏ธ","group":1,"order":2563,"tags":["beach","ocean","person","sport","surf","surfer","surfing","swell","waves"],"version":4,"annotation":"woman surfing","shortcodes":["woman_surfing"],"skins":[{"tone":1,"emoji":"๐Ÿ„๐Ÿปโ€โ™€๏ธ","version":4},{"tone":2,"emoji":"๐Ÿ„๐Ÿผโ€โ™€๏ธ","version":4},{"tone":3,"emoji":"๐Ÿ„๐Ÿฝโ€โ™€๏ธ","version":4},{"tone":4,"emoji":"๐Ÿ„๐Ÿพโ€โ™€๏ธ","version":4},{"tone":5,"emoji":"๐Ÿ„๐Ÿฟโ€โ™€๏ธ","version":4}]},{"emoji":"๐Ÿšฃ","group":1,"order":2575,"tags":["boat","canoe","cruise","fishing","lake","oar","paddle","person","raft","river","row","rowboat","rowing"],"version":1,"annotation":"person rowing boat","shortcodes":["person_rowing_boat","rowboat"],"skins":[{"tone":1,"emoji":"๐Ÿšฃ๐Ÿป","version":1},{"tone":2,"emoji":"๐Ÿšฃ๐Ÿผ","version":1},{"tone":3,"emoji":"๐Ÿšฃ๐Ÿฝ","version":1},{"tone":4,"emoji":"๐Ÿšฃ๐Ÿพ","version":1},{"tone":5,"emoji":"๐Ÿšฃ๐Ÿฟ","version":1}]},{"emoji":"๐Ÿšฃโ€โ™‚๏ธ","group":1,"order":2581,"tags":["boat","canoe","cruise","fishing","lake","man","oar","paddle","raft","river","row","rowboat","rowing"],"version":4,"annotation":"man rowing boat","shortcodes":["man_rowing_boat"],"skins":[{"tone":1,"emoji":"๐Ÿšฃ๐Ÿปโ€โ™‚๏ธ","version":4},{"tone":2,"emoji":"๐Ÿšฃ๐Ÿผโ€โ™‚๏ธ","version":4},{"tone":3,"emoji":"๐Ÿšฃ๐Ÿฝโ€โ™‚๏ธ","version":4},{"tone":4,"emoji":"๐Ÿšฃ๐Ÿพโ€โ™‚๏ธ","version":4},{"tone":5,"emoji":"๐Ÿšฃ๐Ÿฟโ€โ™‚๏ธ","version":4}]},{"emoji":"๐Ÿšฃโ€โ™€๏ธ","group":1,"order":2593,"tags":["boat","canoe","cruise","fishing","lake","oar","paddle","raft","river","row","rowboat","rowing","woman"],"version":4,"annotation":"woman rowing boat","shortcodes":["woman_rowing_boat"],"skins":[{"tone":1,"emoji":"๐Ÿšฃ๐Ÿปโ€โ™€๏ธ","version":4},{"tone":2,"emoji":"๐Ÿšฃ๐Ÿผโ€โ™€๏ธ","version":4},{"tone":3,"emoji":"๐Ÿšฃ๐Ÿฝโ€โ™€๏ธ","version":4},{"tone":4,"emoji":"๐Ÿšฃ๐Ÿพโ€โ™€๏ธ","version":4},{"tone":5,"emoji":"๐Ÿšฃ๐Ÿฟโ€โ™€๏ธ","version":4}]},{"emoji":"๐ŸŠ๏ธ","group":1,"order":2605,"tags":["freestyle","person","sport","swim","swimmer","swimming","triathlon"],"version":0.6,"annotation":"person swimming","shortcodes":["person_swimming","swimmer","swimming"],"skins":[{"tone":1,"emoji":"๐ŸŠ๐Ÿป","version":1},{"tone":2,"emoji":"๐ŸŠ๐Ÿผ","version":1},{"tone":3,"emoji":"๐ŸŠ๐Ÿฝ","version":1},{"tone":4,"emoji":"๐ŸŠ๐Ÿพ","version":1},{"tone":5,"emoji":"๐ŸŠ๐Ÿฟ","version":1}]},{"emoji":"๐ŸŠโ€โ™‚๏ธ","group":1,"order":2611,"tags":["freestyle","man","sport","swim","swimmer","swimming","triathlon"],"version":4,"annotation":"man swimming","shortcodes":["man_swimming"],"skins":[{"tone":1,"emoji":"๐ŸŠ๐Ÿปโ€โ™‚๏ธ","version":4},{"tone":2,"emoji":"๐ŸŠ๐Ÿผโ€โ™‚๏ธ","version":4},{"tone":3,"emoji":"๐ŸŠ๐Ÿฝโ€โ™‚๏ธ","version":4},{"tone":4,"emoji":"๐ŸŠ๐Ÿพโ€โ™‚๏ธ","version":4},{"tone":5,"emoji":"๐ŸŠ๐Ÿฟโ€โ™‚๏ธ","version":4}]},{"emoji":"๐ŸŠโ€โ™€๏ธ","group":1,"order":2623,"tags":["freestyle","man","sport","swim","swimmer","swimming","triathlon"],"version":4,"annotation":"woman swimming","shortcodes":["woman_swimming"],"skins":[{"tone":1,"emoji":"๐ŸŠ๐Ÿปโ€โ™€๏ธ","version":4},{"tone":2,"emoji":"๐ŸŠ๐Ÿผโ€โ™€๏ธ","version":4},{"tone":3,"emoji":"๐ŸŠ๐Ÿฝโ€โ™€๏ธ","version":4},{"tone":4,"emoji":"๐ŸŠ๐Ÿพโ€โ™€๏ธ","version":4},{"tone":5,"emoji":"๐ŸŠ๐Ÿฟโ€โ™€๏ธ","version":4}]},{"emoji":"โ›น๏ธ","group":1,"order":2636,"tags":["athletic","ball","basketball","bouncing","championship","dribble","net","person","player","throw"],"version":0.7,"annotation":"person bouncing ball","shortcodes":["person_bouncing_ball"],"skins":[{"tone":1,"emoji":"โ›น๐Ÿป","version":2},{"tone":2,"emoji":"โ›น๐Ÿผ","version":2},{"tone":3,"emoji":"โ›น๐Ÿฝ","version":2},{"tone":4,"emoji":"โ›น๐Ÿพ","version":2},{"tone":5,"emoji":"โ›น๐Ÿฟ","version":2}]},{"emoji":"โ›น๏ธโ€โ™‚๏ธ","group":1,"order":2642,"tags":["athletic","ball","basketball","bouncing","championship","dribble","man","net","player","throw"],"version":4,"annotation":"man bouncing ball","shortcodes":["man_bouncing_ball"],"skins":[{"tone":1,"emoji":"โ›น๐Ÿปโ€โ™‚๏ธ","version":4},{"tone":2,"emoji":"โ›น๐Ÿผโ€โ™‚๏ธ","version":4},{"tone":3,"emoji":"โ›น๐Ÿฝโ€โ™‚๏ธ","version":4},{"tone":4,"emoji":"โ›น๐Ÿพโ€โ™‚๏ธ","version":4},{"tone":5,"emoji":"โ›น๐Ÿฟโ€โ™‚๏ธ","version":4}]},{"emoji":"โ›น๏ธโ€โ™€๏ธ","group":1,"order":2656,"tags":["athletic","ball","basketball","bouncing","championship","dribble","net","player","throw","woman"],"version":4,"annotation":"woman bouncing ball","shortcodes":["woman_bouncing_ball"],"skins":[{"tone":1,"emoji":"โ›น๐Ÿปโ€โ™€๏ธ","version":4},{"tone":2,"emoji":"โ›น๐Ÿผโ€โ™€๏ธ","version":4},{"tone":3,"emoji":"โ›น๐Ÿฝโ€โ™€๏ธ","version":4},{"tone":4,"emoji":"โ›น๐Ÿพโ€โ™€๏ธ","version":4},{"tone":5,"emoji":"โ›น๐Ÿฟโ€โ™€๏ธ","version":4}]},{"emoji":"๐Ÿ‹๏ธ","group":1,"order":2671,"tags":["barbell","bodybuilder","deadlift","lifter","lifting","person","powerlifting","weight","weightlifter","weights","workout"],"version":0.7,"annotation":"person lifting weights","shortcodes":["person_lifting_weights","weight_lifter","weight_lifting"],"skins":[{"tone":1,"emoji":"๐Ÿ‹๐Ÿป","version":2},{"tone":2,"emoji":"๐Ÿ‹๐Ÿผ","version":2},{"tone":3,"emoji":"๐Ÿ‹๐Ÿฝ","version":2},{"tone":4,"emoji":"๐Ÿ‹๐Ÿพ","version":2},{"tone":5,"emoji":"๐Ÿ‹๐Ÿฟ","version":2}]},{"emoji":"๐Ÿ‹๏ธโ€โ™‚๏ธ","group":1,"order":2677,"tags":["barbell","bodybuilder","deadlift","lifter","lifting","man","powerlifting","weight","weightlifter","weights","workout"],"version":4,"annotation":"man lifting weights","shortcodes":["man_lifting_weights"],"skins":[{"tone":1,"emoji":"๐Ÿ‹๐Ÿปโ€โ™‚๏ธ","version":4},{"tone":2,"emoji":"๐Ÿ‹๐Ÿผโ€โ™‚๏ธ","version":4},{"tone":3,"emoji":"๐Ÿ‹๐Ÿฝโ€โ™‚๏ธ","version":4},{"tone":4,"emoji":"๐Ÿ‹๐Ÿพโ€โ™‚๏ธ","version":4},{"tone":5,"emoji":"๐Ÿ‹๐Ÿฟโ€โ™‚๏ธ","version":4}]},{"emoji":"๐Ÿ‹๏ธโ€โ™€๏ธ","group":1,"order":2691,"tags":["barbell","bodybuilder","deadlift","lifter","lifting","powerlifting","weight","weightlifter","weights","woman","workout"],"version":4,"annotation":"woman lifting weights","shortcodes":["woman_lifting_weights"],"skins":[{"tone":1,"emoji":"๐Ÿ‹๐Ÿปโ€โ™€๏ธ","version":4},{"tone":2,"emoji":"๐Ÿ‹๐Ÿผโ€โ™€๏ธ","version":4},{"tone":3,"emoji":"๐Ÿ‹๐Ÿฝโ€โ™€๏ธ","version":4},{"tone":4,"emoji":"๐Ÿ‹๐Ÿพโ€โ™€๏ธ","version":4},{"tone":5,"emoji":"๐Ÿ‹๐Ÿฟโ€โ™€๏ธ","version":4}]},{"emoji":"๐Ÿšด","group":1,"order":2705,"tags":["bicycle","bicyclist","bike","biking","cycle","cyclist","person","riding","sport"],"version":1,"annotation":"person biking","shortcodes":["bicyclist","biking","person_biking"],"skins":[{"tone":1,"emoji":"๐Ÿšด๐Ÿป","version":1},{"tone":2,"emoji":"๐Ÿšด๐Ÿผ","version":1},{"tone":3,"emoji":"๐Ÿšด๐Ÿฝ","version":1},{"tone":4,"emoji":"๐Ÿšด๐Ÿพ","version":1},{"tone":5,"emoji":"๐Ÿšด๐Ÿฟ","version":1}]},{"emoji":"๐Ÿšดโ€โ™‚๏ธ","group":1,"order":2711,"tags":["bicycle","bicyclist","bike","biking","cycle","cyclist","man","riding","sport"],"version":4,"annotation":"man biking","shortcodes":["man_biking"],"skins":[{"tone":1,"emoji":"๐Ÿšด๐Ÿปโ€โ™‚๏ธ","version":4},{"tone":2,"emoji":"๐Ÿšด๐Ÿผโ€โ™‚๏ธ","version":4},{"tone":3,"emoji":"๐Ÿšด๐Ÿฝโ€โ™‚๏ธ","version":4},{"tone":4,"emoji":"๐Ÿšด๐Ÿพโ€โ™‚๏ธ","version":4},{"tone":5,"emoji":"๐Ÿšด๐Ÿฟโ€โ™‚๏ธ","version":4}]},{"emoji":"๐Ÿšดโ€โ™€๏ธ","group":1,"order":2723,"tags":["bicycle","bicyclist","bike","biking","cycle","cyclist","riding","sport","woman"],"version":4,"annotation":"woman biking","shortcodes":["woman_biking"],"skins":[{"tone":1,"emoji":"๐Ÿšด๐Ÿปโ€โ™€๏ธ","version":4},{"tone":2,"emoji":"๐Ÿšด๐Ÿผโ€โ™€๏ธ","version":4},{"tone":3,"emoji":"๐Ÿšด๐Ÿฝโ€โ™€๏ธ","version":4},{"tone":4,"emoji":"๐Ÿšด๐Ÿพโ€โ™€๏ธ","version":4},{"tone":5,"emoji":"๐Ÿšด๐Ÿฟโ€โ™€๏ธ","version":4}]},{"emoji":"๐Ÿšต","group":1,"order":2735,"tags":["bicycle","bicyclist","bike","biking","cycle","cyclist","mountain","person","riding","sport"],"version":1,"annotation":"person mountain biking","shortcodes":["mountain_bicyclist","mountain_biking","person_mountain_biking"],"skins":[{"tone":1,"emoji":"๐Ÿšต๐Ÿป","version":1},{"tone":2,"emoji":"๐Ÿšต๐Ÿผ","version":1},{"tone":3,"emoji":"๐Ÿšต๐Ÿฝ","version":1},{"tone":4,"emoji":"๐Ÿšต๐Ÿพ","version":1},{"tone":5,"emoji":"๐Ÿšต๐Ÿฟ","version":1}]},{"emoji":"๐Ÿšตโ€โ™‚๏ธ","group":1,"order":2741,"tags":["bicycle","bicyclist","bike","biking","cycle","cyclist","man","mountain","riding","sport"],"version":4,"annotation":"man mountain biking","shortcodes":["man_mountain_biking"],"skins":[{"tone":1,"emoji":"๐Ÿšต๐Ÿปโ€โ™‚๏ธ","version":4},{"tone":2,"emoji":"๐Ÿšต๐Ÿผโ€โ™‚๏ธ","version":4},{"tone":3,"emoji":"๐Ÿšต๐Ÿฝโ€โ™‚๏ธ","version":4},{"tone":4,"emoji":"๐Ÿšต๐Ÿพโ€โ™‚๏ธ","version":4},{"tone":5,"emoji":"๐Ÿšต๐Ÿฟโ€โ™‚๏ธ","version":4}]},{"emoji":"๐Ÿšตโ€โ™€๏ธ","group":1,"order":2753,"tags":["bicycle","bicyclist","bike","biking","cycle","cyclist","mountain","riding","sport","woman"],"version":4,"annotation":"woman mountain biking","shortcodes":["woman_mountain_biking"],"skins":[{"tone":1,"emoji":"๐Ÿšต๐Ÿปโ€โ™€๏ธ","version":4},{"tone":2,"emoji":"๐Ÿšต๐Ÿผโ€โ™€๏ธ","version":4},{"tone":3,"emoji":"๐Ÿšต๐Ÿฝโ€โ™€๏ธ","version":4},{"tone":4,"emoji":"๐Ÿšต๐Ÿพโ€โ™€๏ธ","version":4},{"tone":5,"emoji":"๐Ÿšต๐Ÿฟโ€โ™€๏ธ","version":4}]},{"emoji":"๐Ÿคธ","group":1,"order":2765,"tags":["active","cartwheel","cartwheeling","excited","flip","gymnastics","happy","person","somersault"],"version":3,"annotation":"person cartwheeling","shortcodes":["cartwheeling","person_cartwheel"],"skins":[{"tone":1,"emoji":"๐Ÿคธ๐Ÿป","version":3},{"tone":2,"emoji":"๐Ÿคธ๐Ÿผ","version":3},{"tone":3,"emoji":"๐Ÿคธ๐Ÿฝ","version":3},{"tone":4,"emoji":"๐Ÿคธ๐Ÿพ","version":3},{"tone":5,"emoji":"๐Ÿคธ๐Ÿฟ","version":3}]},{"emoji":"๐Ÿคธโ€โ™‚๏ธ","group":1,"order":2771,"tags":["active","cartwheel","cartwheeling","excited","flip","gymnastics","happy","man","somersault"],"version":4,"annotation":"man cartwheeling","shortcodes":["man_cartwheeling"],"skins":[{"tone":1,"emoji":"๐Ÿคธ๐Ÿปโ€โ™‚๏ธ","version":4},{"tone":2,"emoji":"๐Ÿคธ๐Ÿผโ€โ™‚๏ธ","version":4},{"tone":3,"emoji":"๐Ÿคธ๐Ÿฝโ€โ™‚๏ธ","version":4},{"tone":4,"emoji":"๐Ÿคธ๐Ÿพโ€โ™‚๏ธ","version":4},{"tone":5,"emoji":"๐Ÿคธ๐Ÿฟโ€โ™‚๏ธ","version":4}]},{"emoji":"๐Ÿคธโ€โ™€๏ธ","group":1,"order":2783,"tags":["active","cartwheel","cartwheeling","excited","flip","gymnastics","happy","somersault","woman"],"version":4,"annotation":"woman cartwheeling","shortcodes":["woman_cartwheeling"],"skins":[{"tone":1,"emoji":"๐Ÿคธ๐Ÿปโ€โ™€๏ธ","version":4},{"tone":2,"emoji":"๐Ÿคธ๐Ÿผโ€โ™€๏ธ","version":4},{"tone":3,"emoji":"๐Ÿคธ๐Ÿฝโ€โ™€๏ธ","version":4},{"tone":4,"emoji":"๐Ÿคธ๐Ÿพโ€โ™€๏ธ","version":4},{"tone":5,"emoji":"๐Ÿคธ๐Ÿฟโ€โ™€๏ธ","version":4}]},{"emoji":"๐Ÿคผ","group":1,"order":2795,"tags":["combat","duel","grapple","people","ring","tournament","wrestle","wrestling"],"version":3,"annotation":"people wrestling","shortcodes":["people_wrestling","wrestlers","wrestling"]},{"emoji":"๐Ÿคผโ€โ™‚๏ธ","group":1,"order":2796,"tags":["combat","duel","grapple","men","ring","tournament","wrestle","wrestling"],"version":4,"annotation":"men wrestling","shortcodes":["men_wrestling"]},{"emoji":"๐Ÿคผโ€โ™€๏ธ","group":1,"order":2798,"tags":["combat","duel","grapple","ring","tournament","women","wrestle","wrestling"],"version":4,"annotation":"women wrestling","shortcodes":["women_wrestling"]},{"emoji":"๐Ÿคฝ","group":1,"order":2800,"tags":["person","playing","polo","sport","swimming","water","waterpolo"],"version":3,"annotation":"person playing water polo","shortcodes":["person_playing_water_polo","water_polo"],"skins":[{"tone":1,"emoji":"๐Ÿคฝ๐Ÿป","version":3},{"tone":2,"emoji":"๐Ÿคฝ๐Ÿผ","version":3},{"tone":3,"emoji":"๐Ÿคฝ๐Ÿฝ","version":3},{"tone":4,"emoji":"๐Ÿคฝ๐Ÿพ","version":3},{"tone":5,"emoji":"๐Ÿคฝ๐Ÿฟ","version":3}]},{"emoji":"๐Ÿคฝโ€โ™‚๏ธ","group":1,"order":2806,"tags":["man","playing","polo","sport","swimming","water","waterpolo"],"version":4,"annotation":"man playing water polo","shortcodes":["man_playing_water_polo"],"skins":[{"tone":1,"emoji":"๐Ÿคฝ๐Ÿปโ€โ™‚๏ธ","version":4},{"tone":2,"emoji":"๐Ÿคฝ๐Ÿผโ€โ™‚๏ธ","version":4},{"tone":3,"emoji":"๐Ÿคฝ๐Ÿฝโ€โ™‚๏ธ","version":4},{"tone":4,"emoji":"๐Ÿคฝ๐Ÿพโ€โ™‚๏ธ","version":4},{"tone":5,"emoji":"๐Ÿคฝ๐Ÿฟโ€โ™‚๏ธ","version":4}]},{"emoji":"๐Ÿคฝโ€โ™€๏ธ","group":1,"order":2818,"tags":["playing","polo","sport","swimming","water","waterpolo","woman"],"version":4,"annotation":"woman playing water polo","shortcodes":["woman_playing_water_polo"],"skins":[{"tone":1,"emoji":"๐Ÿคฝ๐Ÿปโ€โ™€๏ธ","version":4},{"tone":2,"emoji":"๐Ÿคฝ๐Ÿผโ€โ™€๏ธ","version":4},{"tone":3,"emoji":"๐Ÿคฝ๐Ÿฝโ€โ™€๏ธ","version":4},{"tone":4,"emoji":"๐Ÿคฝ๐Ÿพโ€โ™€๏ธ","version":4},{"tone":5,"emoji":"๐Ÿคฝ๐Ÿฟโ€โ™€๏ธ","version":4}]},{"emoji":"๐Ÿคพ","group":1,"order":2830,"tags":["athletics","ball","catch","chuck","handball","hurl","lob","person","pitch","playing","sport","throw","toss"],"version":3,"annotation":"person playing handball","shortcodes":["handball","person_playing_handball"],"skins":[{"tone":1,"emoji":"๐Ÿคพ๐Ÿป","version":3},{"tone":2,"emoji":"๐Ÿคพ๐Ÿผ","version":3},{"tone":3,"emoji":"๐Ÿคพ๐Ÿฝ","version":3},{"tone":4,"emoji":"๐Ÿคพ๐Ÿพ","version":3},{"tone":5,"emoji":"๐Ÿคพ๐Ÿฟ","version":3}]},{"emoji":"๐Ÿคพโ€โ™‚๏ธ","group":1,"order":2836,"tags":["athletics","ball","catch","chuck","handball","hurl","lob","man","pitch","playing","sport","throw","toss"],"version":4,"annotation":"man playing handball","shortcodes":["man_playing_handball"],"skins":[{"tone":1,"emoji":"๐Ÿคพ๐Ÿปโ€โ™‚๏ธ","version":4},{"tone":2,"emoji":"๐Ÿคพ๐Ÿผโ€โ™‚๏ธ","version":4},{"tone":3,"emoji":"๐Ÿคพ๐Ÿฝโ€โ™‚๏ธ","version":4},{"tone":4,"emoji":"๐Ÿคพ๐Ÿพโ€โ™‚๏ธ","version":4},{"tone":5,"emoji":"๐Ÿคพ๐Ÿฟโ€โ™‚๏ธ","version":4}]},{"emoji":"๐Ÿคพโ€โ™€๏ธ","group":1,"order":2848,"tags":["athletics","ball","catch","chuck","handball","hurl","lob","pitch","playing","sport","throw","toss","woman"],"version":4,"annotation":"woman playing handball","shortcodes":["woman_playing_handball"],"skins":[{"tone":1,"emoji":"๐Ÿคพ๐Ÿปโ€โ™€๏ธ","version":4},{"tone":2,"emoji":"๐Ÿคพ๐Ÿผโ€โ™€๏ธ","version":4},{"tone":3,"emoji":"๐Ÿคพ๐Ÿฝโ€โ™€๏ธ","version":4},{"tone":4,"emoji":"๐Ÿคพ๐Ÿพโ€โ™€๏ธ","version":4},{"tone":5,"emoji":"๐Ÿคพ๐Ÿฟโ€โ™€๏ธ","version":4}]},{"emoji":"๐Ÿคน","group":1,"order":2860,"tags":["act","balance","balancing","handle","juggle","juggling","manage","multitask","person","skill"],"version":3,"annotation":"person juggling","shortcodes":["juggler","juggling","person_juggling"],"skins":[{"tone":1,"emoji":"๐Ÿคน๐Ÿป","version":3},{"tone":2,"emoji":"๐Ÿคน๐Ÿผ","version":3},{"tone":3,"emoji":"๐Ÿคน๐Ÿฝ","version":3},{"tone":4,"emoji":"๐Ÿคน๐Ÿพ","version":3},{"tone":5,"emoji":"๐Ÿคน๐Ÿฟ","version":3}]},{"emoji":"๐Ÿคนโ€โ™‚๏ธ","group":1,"order":2866,"tags":["act","balance","balancing","handle","juggle","juggling","man","manage","multitask","skill"],"version":4,"annotation":"man juggling","shortcodes":["man_juggling"],"skins":[{"tone":1,"emoji":"๐Ÿคน๐Ÿปโ€โ™‚๏ธ","version":4},{"tone":2,"emoji":"๐Ÿคน๐Ÿผโ€โ™‚๏ธ","version":4},{"tone":3,"emoji":"๐Ÿคน๐Ÿฝโ€โ™‚๏ธ","version":4},{"tone":4,"emoji":"๐Ÿคน๐Ÿพโ€โ™‚๏ธ","version":4},{"tone":5,"emoji":"๐Ÿคน๐Ÿฟโ€โ™‚๏ธ","version":4}]},{"emoji":"๐Ÿคนโ€โ™€๏ธ","group":1,"order":2878,"tags":["act","balance","balancing","handle","juggle","juggling","manage","multitask","skill","woman"],"version":4,"annotation":"woman juggling","shortcodes":["woman_juggling"],"skins":[{"tone":1,"emoji":"๐Ÿคน๐Ÿปโ€โ™€๏ธ","version":4},{"tone":2,"emoji":"๐Ÿคน๐Ÿผโ€โ™€๏ธ","version":4},{"tone":3,"emoji":"๐Ÿคน๐Ÿฝโ€โ™€๏ธ","version":4},{"tone":4,"emoji":"๐Ÿคน๐Ÿพโ€โ™€๏ธ","version":4},{"tone":5,"emoji":"๐Ÿคน๐Ÿฟโ€โ™€๏ธ","version":4}]},{"emoji":"๐Ÿง˜","group":1,"order":2890,"tags":["cross","legged","legs","lotus","meditation","peace","person","position","relax","serenity","yoga","yogi","zen"],"version":5,"annotation":"person in lotus position","shortcodes":["person_in_lotus_position"],"skins":[{"tone":1,"emoji":"๐Ÿง˜๐Ÿป","version":5},{"tone":2,"emoji":"๐Ÿง˜๐Ÿผ","version":5},{"tone":3,"emoji":"๐Ÿง˜๐Ÿฝ","version":5},{"tone":4,"emoji":"๐Ÿง˜๐Ÿพ","version":5},{"tone":5,"emoji":"๐Ÿง˜๐Ÿฟ","version":5}]},{"emoji":"๐Ÿง˜โ€โ™‚๏ธ","group":1,"order":2896,"tags":["cross","legged","legs","lotus","man","meditation","peace","position","relax","serenity","yoga","yogi","zen"],"version":5,"annotation":"man in lotus position","shortcodes":["man_in_lotus_position"],"skins":[{"tone":1,"emoji":"๐Ÿง˜๐Ÿปโ€โ™‚๏ธ","version":5},{"tone":2,"emoji":"๐Ÿง˜๐Ÿผโ€โ™‚๏ธ","version":5},{"tone":3,"emoji":"๐Ÿง˜๐Ÿฝโ€โ™‚๏ธ","version":5},{"tone":4,"emoji":"๐Ÿง˜๐Ÿพโ€โ™‚๏ธ","version":5},{"tone":5,"emoji":"๐Ÿง˜๐Ÿฟโ€โ™‚๏ธ","version":5}]},{"emoji":"๐Ÿง˜โ€โ™€๏ธ","group":1,"order":2908,"tags":["cross","legged","legs","lotus","meditation","peace","position","relax","serenity","woman","yoga","yogi","zen"],"version":5,"annotation":"woman in lotus position","shortcodes":["woman_in_lotus_position"],"skins":[{"tone":1,"emoji":"๐Ÿง˜๐Ÿปโ€โ™€๏ธ","version":5},{"tone":2,"emoji":"๐Ÿง˜๐Ÿผโ€โ™€๏ธ","version":5},{"tone":3,"emoji":"๐Ÿง˜๐Ÿฝโ€โ™€๏ธ","version":5},{"tone":4,"emoji":"๐Ÿง˜๐Ÿพโ€โ™€๏ธ","version":5},{"tone":5,"emoji":"๐Ÿง˜๐Ÿฟโ€โ™€๏ธ","version":5}]},{"emoji":"๐Ÿ›€","group":1,"order":2920,"tags":["bath","bathtub","person","taking","tub"],"version":0.6,"annotation":"person taking bath","shortcodes":["bath","person_taking_bath"],"skins":[{"tone":1,"emoji":"๐Ÿ›€๐Ÿป","version":1},{"tone":2,"emoji":"๐Ÿ›€๐Ÿผ","version":1},{"tone":3,"emoji":"๐Ÿ›€๐Ÿฝ","version":1},{"tone":4,"emoji":"๐Ÿ›€๐Ÿพ","version":1},{"tone":5,"emoji":"๐Ÿ›€๐Ÿฟ","version":1}]},{"emoji":"๐Ÿ›Œ","group":1,"order":2926,"tags":["bed","bedtime","good","goodnight","hotel","nap","night","person","sleep","tired","zzz"],"version":1,"annotation":"person in bed","shortcodes":["person_in_bed","sleeping_accommodation"],"skins":[{"tone":1,"emoji":"๐Ÿ›Œ๐Ÿป","version":4},{"tone":2,"emoji":"๐Ÿ›Œ๐Ÿผ","version":4},{"tone":3,"emoji":"๐Ÿ›Œ๐Ÿฝ","version":4},{"tone":4,"emoji":"๐Ÿ›Œ๐Ÿพ","version":4},{"tone":5,"emoji":"๐Ÿ›Œ๐Ÿฟ","version":4}]},{"emoji":"๐Ÿง‘โ€๐Ÿคโ€๐Ÿง‘","group":1,"order":2932,"tags":["bae","bestie","bff","couple","dating","flirt","friends","hand","hold","people","twins"],"version":12,"annotation":"people holding hands","shortcodes":["people_holding_hands"],"skins":[{"tone":1,"emoji":"๐Ÿง‘๐Ÿปโ€๐Ÿคโ€๐Ÿง‘๐Ÿป","version":12},{"tone":[1,2],"emoji":"๐Ÿง‘๐Ÿปโ€๐Ÿคโ€๐Ÿง‘๐Ÿผ","version":12.1},{"tone":[1,3],"emoji":"๐Ÿง‘๐Ÿปโ€๐Ÿคโ€๐Ÿง‘๐Ÿฝ","version":12.1},{"tone":[1,4],"emoji":"๐Ÿง‘๐Ÿปโ€๐Ÿคโ€๐Ÿง‘๐Ÿพ","version":12.1},{"tone":[1,5],"emoji":"๐Ÿง‘๐Ÿปโ€๐Ÿคโ€๐Ÿง‘๐Ÿฟ","version":12.1},{"tone":[2,1],"emoji":"๐Ÿง‘๐Ÿผโ€๐Ÿคโ€๐Ÿง‘๐Ÿป","version":12},{"tone":2,"emoji":"๐Ÿง‘๐Ÿผโ€๐Ÿคโ€๐Ÿง‘๐Ÿผ","version":12},{"tone":[2,3],"emoji":"๐Ÿง‘๐Ÿผโ€๐Ÿคโ€๐Ÿง‘๐Ÿฝ","version":12.1},{"tone":[2,4],"emoji":"๐Ÿง‘๐Ÿผโ€๐Ÿคโ€๐Ÿง‘๐Ÿพ","version":12.1},{"tone":[2,5],"emoji":"๐Ÿง‘๐Ÿผโ€๐Ÿคโ€๐Ÿง‘๐Ÿฟ","version":12.1},{"tone":[3,1],"emoji":"๐Ÿง‘๐Ÿฝโ€๐Ÿคโ€๐Ÿง‘๐Ÿป","version":12},{"tone":[3,2],"emoji":"๐Ÿง‘๐Ÿฝโ€๐Ÿคโ€๐Ÿง‘๐Ÿผ","version":12},{"tone":3,"emoji":"๐Ÿง‘๐Ÿฝโ€๐Ÿคโ€๐Ÿง‘๐Ÿฝ","version":12},{"tone":[3,4],"emoji":"๐Ÿง‘๐Ÿฝโ€๐Ÿคโ€๐Ÿง‘๐Ÿพ","version":12.1},{"tone":[3,5],"emoji":"๐Ÿง‘๐Ÿฝโ€๐Ÿคโ€๐Ÿง‘๐Ÿฟ","version":12.1},{"tone":[4,1],"emoji":"๐Ÿง‘๐Ÿพโ€๐Ÿคโ€๐Ÿง‘๐Ÿป","version":12},{"tone":[4,2],"emoji":"๐Ÿง‘๐Ÿพโ€๐Ÿคโ€๐Ÿง‘๐Ÿผ","version":12},{"tone":[4,3],"emoji":"๐Ÿง‘๐Ÿพโ€๐Ÿคโ€๐Ÿง‘๐Ÿฝ","version":12},{"tone":4,"emoji":"๐Ÿง‘๐Ÿพโ€๐Ÿคโ€๐Ÿง‘๐Ÿพ","version":12},{"tone":[4,5],"emoji":"๐Ÿง‘๐Ÿพโ€๐Ÿคโ€๐Ÿง‘๐Ÿฟ","version":12.1},{"tone":[5,1],"emoji":"๐Ÿง‘๐Ÿฟโ€๐Ÿคโ€๐Ÿง‘๐Ÿป","version":12},{"tone":[5,2],"emoji":"๐Ÿง‘๐Ÿฟโ€๐Ÿคโ€๐Ÿง‘๐Ÿผ","version":12},{"tone":[5,3],"emoji":"๐Ÿง‘๐Ÿฟโ€๐Ÿคโ€๐Ÿง‘๐Ÿฝ","version":12},{"tone":[5,4],"emoji":"๐Ÿง‘๐Ÿฟโ€๐Ÿคโ€๐Ÿง‘๐Ÿพ","version":12},{"tone":5,"emoji":"๐Ÿง‘๐Ÿฟโ€๐Ÿคโ€๐Ÿง‘๐Ÿฟ","version":12}]},{"emoji":"๐Ÿ‘ญ","group":1,"order":2958,"tags":["bae","bestie","bff","couple","dating","flirt","friends","girls","hand","hold","sisters","twins","women"],"version":1,"annotation":"women holding hands","shortcodes":["two_women_holding_hands"],"skins":[{"tone":1,"emoji":"๐Ÿ‘ญ๐Ÿป","version":12},{"tone":2,"emoji":"๐Ÿ‘ญ๐Ÿผ","version":12},{"tone":3,"emoji":"๐Ÿ‘ญ๐Ÿฝ","version":12},{"tone":4,"emoji":"๐Ÿ‘ญ๐Ÿพ","version":12},{"tone":5,"emoji":"๐Ÿ‘ญ๐Ÿฟ","version":12},{"tone":[1,2],"emoji":"๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿผ","version":12.1},{"tone":[1,3],"emoji":"๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฝ","version":12.1},{"tone":[1,4],"emoji":"๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿพ","version":12.1},{"tone":[1,5],"emoji":"๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฟ","version":12.1},{"tone":[2,1],"emoji":"๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿป","version":12},{"tone":[2,3],"emoji":"๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฝ","version":12.1},{"tone":[2,4],"emoji":"๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿพ","version":12.1},{"tone":[2,5],"emoji":"๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฟ","version":12.1},{"tone":[3,1],"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿป","version":12},{"tone":[3,2],"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿผ","version":12},{"tone":[3,4],"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿพ","version":12.1},{"tone":[3,5],"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฟ","version":12.1},{"tone":[4,1],"emoji":"๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿป","version":12},{"tone":[4,2],"emoji":"๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿผ","version":12},{"tone":[4,3],"emoji":"๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฝ","version":12},{"tone":[4,5],"emoji":"๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฟ","version":12.1},{"tone":[5,1],"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿป","version":12},{"tone":[5,2],"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿผ","version":12},{"tone":[5,3],"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿฝ","version":12},{"tone":[5,4],"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘ฉ๐Ÿพ","version":12}]},{"emoji":"๐Ÿ‘ซ","group":1,"order":2984,"tags":["bae","bestie","bff","couple","dating","flirt","friends","hand","hold","man","twins","woman"],"version":0.6,"annotation":"woman and man holding hands","shortcodes":["couple"],"skins":[{"tone":1,"emoji":"๐Ÿ‘ซ๐Ÿป","version":12},{"tone":2,"emoji":"๐Ÿ‘ซ๐Ÿผ","version":12},{"tone":3,"emoji":"๐Ÿ‘ซ๐Ÿฝ","version":12},{"tone":4,"emoji":"๐Ÿ‘ซ๐Ÿพ","version":12},{"tone":5,"emoji":"๐Ÿ‘ซ๐Ÿฟ","version":12},{"tone":[1,2],"emoji":"๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ","version":12},{"tone":[1,3],"emoji":"๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ","version":12},{"tone":[1,4],"emoji":"๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ","version":12},{"tone":[1,5],"emoji":"๐Ÿ‘ฉ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ","version":12},{"tone":[2,1],"emoji":"๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป","version":12},{"tone":[2,3],"emoji":"๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ","version":12},{"tone":[2,4],"emoji":"๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ","version":12},{"tone":[2,5],"emoji":"๐Ÿ‘ฉ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ","version":12},{"tone":[3,1],"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป","version":12},{"tone":[3,2],"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ","version":12},{"tone":[3,4],"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ","version":12},{"tone":[3,5],"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ","version":12},{"tone":[4,1],"emoji":"๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป","version":12},{"tone":[4,2],"emoji":"๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ","version":12},{"tone":[4,3],"emoji":"๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ","version":12},{"tone":[4,5],"emoji":"๐Ÿ‘ฉ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ","version":12},{"tone":[5,1],"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป","version":12},{"tone":[5,2],"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ","version":12},{"tone":[5,3],"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ","version":12},{"tone":[5,4],"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ","version":12}]},{"emoji":"๐Ÿ‘ฌ","group":1,"order":3010,"tags":["bae","bestie","bff","boys","brothers","couple","dating","flirt","friends","hand","hold","men","twins"],"version":1,"annotation":"men holding hands","shortcodes":["two_men_holding_hands"],"skins":[{"tone":1,"emoji":"๐Ÿ‘ฌ๐Ÿป","version":12},{"tone":2,"emoji":"๐Ÿ‘ฌ๐Ÿผ","version":12},{"tone":3,"emoji":"๐Ÿ‘ฌ๐Ÿฝ","version":12},{"tone":4,"emoji":"๐Ÿ‘ฌ๐Ÿพ","version":12},{"tone":5,"emoji":"๐Ÿ‘ฌ๐Ÿฟ","version":12},{"tone":[1,2],"emoji":"๐Ÿ‘จ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ","version":12.1},{"tone":[1,3],"emoji":"๐Ÿ‘จ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ","version":12.1},{"tone":[1,4],"emoji":"๐Ÿ‘จ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ","version":12.1},{"tone":[1,5],"emoji":"๐Ÿ‘จ๐Ÿปโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ","version":12.1},{"tone":[2,1],"emoji":"๐Ÿ‘จ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป","version":12},{"tone":[2,3],"emoji":"๐Ÿ‘จ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ","version":12.1},{"tone":[2,4],"emoji":"๐Ÿ‘จ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ","version":12.1},{"tone":[2,5],"emoji":"๐Ÿ‘จ๐Ÿผโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ","version":12.1},{"tone":[3,1],"emoji":"๐Ÿ‘จ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป","version":12},{"tone":[3,2],"emoji":"๐Ÿ‘จ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ","version":12},{"tone":[3,4],"emoji":"๐Ÿ‘จ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ","version":12.1},{"tone":[3,5],"emoji":"๐Ÿ‘จ๐Ÿฝโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ","version":12.1},{"tone":[4,1],"emoji":"๐Ÿ‘จ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป","version":12},{"tone":[4,2],"emoji":"๐Ÿ‘จ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ","version":12},{"tone":[4,3],"emoji":"๐Ÿ‘จ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ","version":12},{"tone":[4,5],"emoji":"๐Ÿ‘จ๐Ÿพโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฟ","version":12.1},{"tone":[5,1],"emoji":"๐Ÿ‘จ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿป","version":12},{"tone":[5,2],"emoji":"๐Ÿ‘จ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿผ","version":12},{"tone":[5,3],"emoji":"๐Ÿ‘จ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿฝ","version":12},{"tone":[5,4],"emoji":"๐Ÿ‘จ๐Ÿฟโ€๐Ÿคโ€๐Ÿ‘จ๐Ÿพ","version":12}]},{"emoji":"๐Ÿ’","group":1,"order":3036,"tags":["anniversary","babe","bae","couple","date","dating","heart","love","mwah","person","romance","together","xoxo"],"version":0.6,"annotation":"kiss","shortcodes":["couple_kiss","couplekiss"],"skins":[{"tone":1,"emoji":"๐Ÿ’๐Ÿป","version":13.1},{"tone":2,"emoji":"๐Ÿ’๐Ÿผ","version":13.1},{"tone":3,"emoji":"๐Ÿ’๐Ÿฝ","version":13.1},{"tone":4,"emoji":"๐Ÿ’๐Ÿพ","version":13.1},{"tone":5,"emoji":"๐Ÿ’๐Ÿฟ","version":13.1},{"tone":[1,2],"emoji":"๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ","version":13.1},{"tone":[1,3],"emoji":"๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ","version":13.1},{"tone":[1,4],"emoji":"๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ","version":13.1},{"tone":[1,5],"emoji":"๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ","version":13.1},{"tone":[2,1],"emoji":"๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป","version":13.1},{"tone":[2,3],"emoji":"๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ","version":13.1},{"tone":[2,4],"emoji":"๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ","version":13.1},{"tone":[2,5],"emoji":"๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ","version":13.1},{"tone":[3,1],"emoji":"๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป","version":13.1},{"tone":[3,2],"emoji":"๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ","version":13.1},{"tone":[3,4],"emoji":"๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ","version":13.1},{"tone":[3,5],"emoji":"๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ","version":13.1},{"tone":[4,1],"emoji":"๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป","version":13.1},{"tone":[4,2],"emoji":"๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ","version":13.1},{"tone":[4,3],"emoji":"๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ","version":13.1},{"tone":[4,5],"emoji":"๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ","version":13.1},{"tone":[5,1],"emoji":"๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป","version":13.1},{"tone":[5,2],"emoji":"๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ","version":13.1},{"tone":[5,3],"emoji":"๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ","version":13.1},{"tone":[5,4],"emoji":"๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ","version":13.1}]},{"emoji":"๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ","group":1,"order":3082,"tags":["anniversary","babe","bae","couple","date","dating","heart","kiss","love","man","mwah","person","romance","together","woman","xoxo"],"version":2,"annotation":"kiss: woman, man","shortcodes":["kiss_mw","kiss_wm"],"skins":[{"tone":1,"emoji":"๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป","version":13.1},{"tone":[1,2],"emoji":"๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ","version":13.1},{"tone":[1,3],"emoji":"๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ","version":13.1},{"tone":[1,4],"emoji":"๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ","version":13.1},{"tone":[1,5],"emoji":"๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ","version":13.1},{"tone":[2,1],"emoji":"๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป","version":13.1},{"tone":2,"emoji":"๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ","version":13.1},{"tone":[2,3],"emoji":"๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ","version":13.1},{"tone":[2,4],"emoji":"๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ","version":13.1},{"tone":[2,5],"emoji":"๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ","version":13.1},{"tone":[3,1],"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป","version":13.1},{"tone":[3,2],"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ","version":13.1},{"tone":3,"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ","version":13.1},{"tone":[3,4],"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ","version":13.1},{"tone":[3,5],"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ","version":13.1},{"tone":[4,1],"emoji":"๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป","version":13.1},{"tone":[4,2],"emoji":"๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ","version":13.1},{"tone":[4,3],"emoji":"๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ","version":13.1},{"tone":4,"emoji":"๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ","version":13.1},{"tone":[4,5],"emoji":"๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ","version":13.1},{"tone":[5,1],"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป","version":13.1},{"tone":[5,2],"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ","version":13.1},{"tone":[5,3],"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ","version":13.1},{"tone":[5,4],"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ","version":13.1},{"tone":5,"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ","version":13.1}]},{"emoji":"๐Ÿ‘จโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ","group":1,"order":3134,"tags":["anniversary","babe","bae","couple","date","dating","heart","kiss","love","man","mwah","person","romance","together","xoxo"],"version":2,"annotation":"kiss: man, man","shortcodes":["kiss_mm"],"skins":[{"tone":1,"emoji":"๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป","version":13.1},{"tone":[1,2],"emoji":"๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ","version":13.1},{"tone":[1,3],"emoji":"๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ","version":13.1},{"tone":[1,4],"emoji":"๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ","version":13.1},{"tone":[1,5],"emoji":"๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ","version":13.1},{"tone":[2,1],"emoji":"๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป","version":13.1},{"tone":2,"emoji":"๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ","version":13.1},{"tone":[2,3],"emoji":"๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ","version":13.1},{"tone":[2,4],"emoji":"๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ","version":13.1},{"tone":[2,5],"emoji":"๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ","version":13.1},{"tone":[3,1],"emoji":"๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป","version":13.1},{"tone":[3,2],"emoji":"๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ","version":13.1},{"tone":3,"emoji":"๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ","version":13.1},{"tone":[3,4],"emoji":"๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ","version":13.1},{"tone":[3,5],"emoji":"๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ","version":13.1},{"tone":[4,1],"emoji":"๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป","version":13.1},{"tone":[4,2],"emoji":"๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ","version":13.1},{"tone":[4,3],"emoji":"๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ","version":13.1},{"tone":4,"emoji":"๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ","version":13.1},{"tone":[4,5],"emoji":"๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ","version":13.1},{"tone":[5,1],"emoji":"๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป","version":13.1},{"tone":[5,2],"emoji":"๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ","version":13.1},{"tone":[5,3],"emoji":"๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ","version":13.1},{"tone":[5,4],"emoji":"๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ","version":13.1},{"tone":5,"emoji":"๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ","version":13.1}]},{"emoji":"๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ","group":1,"order":3186,"tags":["anniversary","babe","bae","couple","date","dating","heart","kiss","love","mwah","person","romance","together","woman","xoxo"],"version":2,"annotation":"kiss: woman, woman","shortcodes":["kiss_ww"],"skins":[{"tone":1,"emoji":"๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป","version":13.1},{"tone":[1,2],"emoji":"๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ","version":13.1},{"tone":[1,3],"emoji":"๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ","version":13.1},{"tone":[1,4],"emoji":"๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ","version":13.1},{"tone":[1,5],"emoji":"๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ","version":13.1},{"tone":[2,1],"emoji":"๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป","version":13.1},{"tone":2,"emoji":"๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ","version":13.1},{"tone":[2,3],"emoji":"๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ","version":13.1},{"tone":[2,4],"emoji":"๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ","version":13.1},{"tone":[2,5],"emoji":"๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ","version":13.1},{"tone":[3,1],"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป","version":13.1},{"tone":[3,2],"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ","version":13.1},{"tone":3,"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ","version":13.1},{"tone":[3,4],"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ","version":13.1},{"tone":[3,5],"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ","version":13.1},{"tone":[4,1],"emoji":"๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป","version":13.1},{"tone":[4,2],"emoji":"๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ","version":13.1},{"tone":[4,3],"emoji":"๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ","version":13.1},{"tone":4,"emoji":"๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ","version":13.1},{"tone":[4,5],"emoji":"๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ","version":13.1},{"tone":[5,1],"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป","version":13.1},{"tone":[5,2],"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ","version":13.1},{"tone":[5,3],"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ","version":13.1},{"tone":[5,4],"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ","version":13.1},{"tone":5,"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ","version":13.1}]},{"emoji":"๐Ÿ’‘","group":1,"order":3238,"tags":["anniversary","babe","bae","couple","dating","heart","kiss","love","person","relationship","romance","together","you"],"version":0.6,"annotation":"couple with heart","shortcodes":["couple_with_heart"],"skins":[{"tone":1,"emoji":"๐Ÿ’‘๐Ÿป","version":13.1},{"tone":2,"emoji":"๐Ÿ’‘๐Ÿผ","version":13.1},{"tone":3,"emoji":"๐Ÿ’‘๐Ÿฝ","version":13.1},{"tone":4,"emoji":"๐Ÿ’‘๐Ÿพ","version":13.1},{"tone":5,"emoji":"๐Ÿ’‘๐Ÿฟ","version":13.1},{"tone":[1,2],"emoji":"๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿง‘๐Ÿผ","version":13.1},{"tone":[1,3],"emoji":"๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿง‘๐Ÿฝ","version":13.1},{"tone":[1,4],"emoji":"๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿง‘๐Ÿพ","version":13.1},{"tone":[1,5],"emoji":"๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿง‘๐Ÿฟ","version":13.1},{"tone":[2,1],"emoji":"๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿง‘๐Ÿป","version":13.1},{"tone":[2,3],"emoji":"๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿง‘๐Ÿฝ","version":13.1},{"tone":[2,4],"emoji":"๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿง‘๐Ÿพ","version":13.1},{"tone":[2,5],"emoji":"๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿง‘๐Ÿฟ","version":13.1},{"tone":[3,1],"emoji":"๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿง‘๐Ÿป","version":13.1},{"tone":[3,2],"emoji":"๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿง‘๐Ÿผ","version":13.1},{"tone":[3,4],"emoji":"๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿง‘๐Ÿพ","version":13.1},{"tone":[3,5],"emoji":"๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿง‘๐Ÿฟ","version":13.1},{"tone":[4,1],"emoji":"๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿง‘๐Ÿป","version":13.1},{"tone":[4,2],"emoji":"๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿง‘๐Ÿผ","version":13.1},{"tone":[4,3],"emoji":"๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿง‘๐Ÿฝ","version":13.1},{"tone":[4,5],"emoji":"๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿง‘๐Ÿฟ","version":13.1},{"tone":[5,1],"emoji":"๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿง‘๐Ÿป","version":13.1},{"tone":[5,2],"emoji":"๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿง‘๐Ÿผ","version":13.1},{"tone":[5,3],"emoji":"๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿง‘๐Ÿฝ","version":13.1},{"tone":[5,4],"emoji":"๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿง‘๐Ÿพ","version":13.1}]},{"emoji":"๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘จ","group":1,"order":3284,"tags":["anniversary","babe","bae","couple","dating","heart","kiss","love","man","person","relationship","romance","together","woman","you"],"version":2,"annotation":"couple with heart: woman, man","shortcodes":["couple_with_heart_mw","couple_with_heart_wm"],"skins":[{"tone":1,"emoji":"๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป","version":13.1},{"tone":[1,2],"emoji":"๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ","version":13.1},{"tone":[1,3],"emoji":"๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ","version":13.1},{"tone":[1,4],"emoji":"๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ","version":13.1},{"tone":[1,5],"emoji":"๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ","version":13.1},{"tone":[2,1],"emoji":"๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป","version":13.1},{"tone":2,"emoji":"๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ","version":13.1},{"tone":[2,3],"emoji":"๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ","version":13.1},{"tone":[2,4],"emoji":"๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ","version":13.1},{"tone":[2,5],"emoji":"๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ","version":13.1},{"tone":[3,1],"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป","version":13.1},{"tone":[3,2],"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ","version":13.1},{"tone":3,"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ","version":13.1},{"tone":[3,4],"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ","version":13.1},{"tone":[3,5],"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ","version":13.1},{"tone":[4,1],"emoji":"๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป","version":13.1},{"tone":[4,2],"emoji":"๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ","version":13.1},{"tone":[4,3],"emoji":"๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ","version":13.1},{"tone":4,"emoji":"๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ","version":13.1},{"tone":[4,5],"emoji":"๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ","version":13.1},{"tone":[5,1],"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป","version":13.1},{"tone":[5,2],"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ","version":13.1},{"tone":[5,3],"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ","version":13.1},{"tone":[5,4],"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ","version":13.1},{"tone":5,"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ","version":13.1}]},{"emoji":"๐Ÿ‘จโ€โค๏ธโ€๐Ÿ‘จ","group":1,"order":3336,"tags":["anniversary","babe","bae","couple","dating","heart","kiss","love","man","person","relationship","romance","together","you"],"version":2,"annotation":"couple with heart: man, man","shortcodes":["couple_with_heart_mm"],"skins":[{"tone":1,"emoji":"๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป","version":13.1},{"tone":[1,2],"emoji":"๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ","version":13.1},{"tone":[1,3],"emoji":"๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ","version":13.1},{"tone":[1,4],"emoji":"๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ","version":13.1},{"tone":[1,5],"emoji":"๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ","version":13.1},{"tone":[2,1],"emoji":"๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป","version":13.1},{"tone":2,"emoji":"๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ","version":13.1},{"tone":[2,3],"emoji":"๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ","version":13.1},{"tone":[2,4],"emoji":"๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ","version":13.1},{"tone":[2,5],"emoji":"๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ","version":13.1},{"tone":[3,1],"emoji":"๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป","version":13.1},{"tone":[3,2],"emoji":"๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ","version":13.1},{"tone":3,"emoji":"๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ","version":13.1},{"tone":[3,4],"emoji":"๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ","version":13.1},{"tone":[3,5],"emoji":"๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ","version":13.1},{"tone":[4,1],"emoji":"๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป","version":13.1},{"tone":[4,2],"emoji":"๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ","version":13.1},{"tone":[4,3],"emoji":"๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ","version":13.1},{"tone":4,"emoji":"๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ","version":13.1},{"tone":[4,5],"emoji":"๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ","version":13.1},{"tone":[5,1],"emoji":"๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป","version":13.1},{"tone":[5,2],"emoji":"๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ","version":13.1},{"tone":[5,3],"emoji":"๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ","version":13.1},{"tone":[5,4],"emoji":"๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ","version":13.1},{"tone":5,"emoji":"๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ","version":13.1}]},{"emoji":"๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘ฉ","group":1,"order":3388,"tags":["anniversary","babe","bae","couple","dating","heart","kiss","love","person","relationship","romance","together","woman","you"],"version":2,"annotation":"couple with heart: woman, woman","shortcodes":["couple_with_heart_ww"],"skins":[{"tone":1,"emoji":"๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿป","version":13.1},{"tone":[1,2],"emoji":"๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿผ","version":13.1},{"tone":[1,3],"emoji":"๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฝ","version":13.1},{"tone":[1,4],"emoji":"๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿพ","version":13.1},{"tone":[1,5],"emoji":"๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฟ","version":13.1},{"tone":[2,1],"emoji":"๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿป","version":13.1},{"tone":2,"emoji":"๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿผ","version":13.1},{"tone":[2,3],"emoji":"๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฝ","version":13.1},{"tone":[2,4],"emoji":"๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿพ","version":13.1},{"tone":[2,5],"emoji":"๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฟ","version":13.1},{"tone":[3,1],"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿป","version":13.1},{"tone":[3,2],"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿผ","version":13.1},{"tone":3,"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฝ","version":13.1},{"tone":[3,4],"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿพ","version":13.1},{"tone":[3,5],"emoji":"๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฟ","version":13.1},{"tone":[4,1],"emoji":"๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿป","version":13.1},{"tone":[4,2],"emoji":"๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿผ","version":13.1},{"tone":[4,3],"emoji":"๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฝ","version":13.1},{"tone":4,"emoji":"๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿพ","version":13.1},{"tone":[4,5],"emoji":"๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฟ","version":13.1},{"tone":[5,1],"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿป","version":13.1},{"tone":[5,2],"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿผ","version":13.1},{"tone":[5,3],"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฝ","version":13.1},{"tone":[5,4],"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿพ","version":13.1},{"tone":5,"emoji":"๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฟ","version":13.1}]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ","group":1,"order":3440,"tags":["boy","child","family","man","woman"],"version":2,"annotation":"family: man, woman, boy","shortcodes":["family_mwb"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง","group":1,"order":3441,"tags":["child","family","girl","man","woman"],"version":2,"annotation":"family: man, woman, girl","shortcodes":["family_mwg"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","group":1,"order":3442,"tags":["boy","child","family","girl","man","woman"],"version":2,"annotation":"family: man, woman, girl, boy","shortcodes":["family_mwgb"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","group":1,"order":3443,"tags":["boy","child","family","man","woman"],"version":2,"annotation":"family: man, woman, boy, boy","shortcodes":["family_mwbb"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง","group":1,"order":3444,"tags":["child","family","girl","man","woman"],"version":2,"annotation":"family: man, woman, girl, girl","shortcodes":["family_mwgg"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆ","group":1,"order":3445,"tags":["boy","child","family","man"],"version":2,"annotation":"family: man, man, boy","shortcodes":["family_mmb"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ง","group":1,"order":3446,"tags":["child","family","girl","man"],"version":2,"annotation":"family: man, man, girl","shortcodes":["family_mmg"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","group":1,"order":3447,"tags":["boy","child","family","girl","man"],"version":2,"annotation":"family: man, man, girl, boy","shortcodes":["family_mmgb"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","group":1,"order":3448,"tags":["boy","child","family","man"],"version":2,"annotation":"family: man, man, boy, boy","shortcodes":["family_mmbb"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง","group":1,"order":3449,"tags":["child","family","girl","man"],"version":2,"annotation":"family: man, man, girl, girl","shortcodes":["family_mmgg"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ","group":1,"order":3450,"tags":["boy","child","family","woman"],"version":2,"annotation":"family: woman, woman, boy","shortcodes":["family_wwb"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ง","group":1,"order":3451,"tags":["child","family","girl","woman"],"version":2,"annotation":"family: woman, woman, girl","shortcodes":["family_wwg"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","group":1,"order":3452,"tags":["boy","child","family","girl","woman"],"version":2,"annotation":"family: woman, woman, girl, boy","shortcodes":["family_wwgb"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","group":1,"order":3453,"tags":["boy","child","family","woman"],"version":2,"annotation":"family: woman, woman, boy, boy","shortcodes":["family_wwbb"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง","group":1,"order":3454,"tags":["child","family","girl","woman"],"version":2,"annotation":"family: woman, woman, girl, girl","shortcodes":["family_wwgg"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘ฆ","group":1,"order":3455,"tags":["boy","child","family","man"],"version":4,"annotation":"family: man, boy","shortcodes":["family_mb"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","group":1,"order":3456,"tags":["boy","child","family","man"],"version":4,"annotation":"family: man, boy, boy","shortcodes":["family_mbb"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘ง","group":1,"order":3457,"tags":["child","family","girl","man"],"version":4,"annotation":"family: man, girl","shortcodes":["family_mg"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","group":1,"order":3458,"tags":["boy","child","family","girl","man"],"version":4,"annotation":"family: man, girl, boy","shortcodes":["family_mgb"]},{"emoji":"๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง","group":1,"order":3459,"tags":["child","family","girl","man"],"version":4,"annotation":"family: man, girl, girl","shortcodes":["family_mgg"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘ฆ","group":1,"order":3460,"tags":["boy","child","family","woman"],"version":4,"annotation":"family: woman, boy","shortcodes":["family_wb"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","group":1,"order":3461,"tags":["boy","child","family","woman"],"version":4,"annotation":"family: woman, boy, boy","shortcodes":["family_wbb"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘ง","group":1,"order":3462,"tags":["child","family","girl","woman"],"version":4,"annotation":"family: woman, girl","shortcodes":["family_wg"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","group":1,"order":3463,"tags":["boy","child","family","girl","woman"],"version":4,"annotation":"family: woman, girl, boy","shortcodes":["family_wgb"]},{"emoji":"๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง","group":1,"order":3464,"tags":["child","family","girl","woman"],"version":4,"annotation":"family: woman, girl, girl","shortcodes":["family_wgg"]},{"emoji":"๐Ÿ—ฃ๏ธ","group":1,"order":3466,"tags":["face","head","silhouette","speak","speaking"],"version":0.7,"annotation":"speaking head","shortcodes":["speaking_head"]},{"emoji":"๐Ÿ‘ค","group":1,"order":3467,"tags":["bust","mysterious","shadow","silhouette"],"version":0.6,"annotation":"bust in silhouette","shortcodes":["bust_in_silhouette"]},{"emoji":"๐Ÿ‘ฅ","group":1,"order":3468,"tags":["bff","bust","busts","everyone","friend","friends","people","silhouette"],"version":1,"annotation":"busts in silhouette","shortcodes":["busts_in_silhouette"]},{"emoji":"๐Ÿซ‚","group":1,"order":3469,"tags":["comfort","embrace","farewell","friendship","goodbye","hello","hug","hugging","love","people","thanks"],"version":13,"annotation":"people hugging","shortcodes":["people_hugging"]},{"emoji":"๐Ÿ‘ช๏ธ","group":1,"order":3470,"tags":["child"],"version":0.6,"annotation":"family","shortcodes":["family"]},{"emoji":"๐Ÿง‘โ€๐Ÿง‘โ€๐Ÿง’","group":1,"order":3471,"tags":["adult","child","family"],"version":15.1,"annotation":"family: adult, adult, child","shortcodes":["family_aac"]},{"emoji":"๐Ÿง‘โ€๐Ÿง‘โ€๐Ÿง’โ€๐Ÿง’","group":1,"order":3472,"tags":["adult","child","family"],"version":15.1,"annotation":"family: adult, adult, child, child","shortcodes":["family_aacc"]},{"emoji":"๐Ÿง‘โ€๐Ÿง’","group":1,"order":3473,"tags":["adult","child","family"],"version":15.1,"annotation":"family: adult, child","shortcodes":["family_ac"]},{"emoji":"๐Ÿง‘โ€๐Ÿง’โ€๐Ÿง’","group":1,"order":3474,"tags":["adult","child","family"],"version":15.1,"annotation":"family: adult, child, child","shortcodes":["family_acc"]},{"emoji":"๐Ÿ‘ฃ","group":1,"order":3475,"tags":["barefoot","clothing","footprint","omw","print","walk"],"version":0.6,"annotation":"footprints","shortcodes":["footprints"]},{"emoji":"๐Ÿซ†","group":1,"order":3476,"tags":["clue","crime","detective","forensics","identity","mystery","print","safety","trace"],"version":16,"annotation":"fingerprint","shortcodes":["fingerprint"]},{"emoji":"๐Ÿป","group":2,"order":3477,"tags":["1โ€“2","light","skin","tone","type"],"version":1,"annotation":"light skin tone","shortcodes":["tone1","tone_light"]},{"emoji":"๐Ÿผ","group":2,"order":3478,"tags":["3","medium-light","skin","tone","type"],"version":1,"annotation":"medium-light skin tone","shortcodes":["tone2","tone_medium_light"]},{"emoji":"๐Ÿฝ","group":2,"order":3479,"tags":["4","medium","skin","tone","type"],"version":1,"annotation":"medium skin tone","shortcodes":["tone3","tone_medium"]},{"emoji":"๐Ÿพ","group":2,"order":3480,"tags":["5","medium-dark","skin","tone","type"],"version":1,"annotation":"medium-dark skin tone","shortcodes":["tone4","tone_medium_dark"]},{"emoji":"๐Ÿฟ","group":2,"order":3481,"tags":["6","dark","skin","tone","type"],"version":1,"annotation":"dark skin tone","shortcodes":["tone5","tone_dark"]},{"emoji":"๐Ÿฆฐ","group":2,"order":3482,"tags":["ginger","hair","red","redhead"],"version":11,"annotation":"red hair","shortcodes":["red_hair"]},{"emoji":"๐Ÿฆฑ","group":2,"order":3483,"tags":["afro","curly","hair","ringlets"],"version":11,"annotation":"curly hair","shortcodes":["curly_hair"]},{"emoji":"๐Ÿฆณ","group":2,"order":3484,"tags":["gray","hair","old","white"],"version":11,"annotation":"white hair","shortcodes":["white_hair"]},{"emoji":"๐Ÿฆฒ","group":2,"order":3485,"tags":["chemotherapy","hair","hairless","no","shaven"],"version":11,"annotation":"bald","shortcodes":["no_hair"]},{"emoji":"๐Ÿต","group":3,"order":3486,"tags":["animal","banana","face","monkey"],"version":0.6,"annotation":"monkey face","shortcodes":["monkey_face"]},{"emoji":"๐Ÿ’","group":3,"order":3487,"tags":["animal","banana"],"version":0.6,"annotation":"monkey","shortcodes":["monkey"]},{"emoji":"๐Ÿฆ","group":3,"order":3488,"tags":["animal"],"version":3,"annotation":"gorilla","shortcodes":["gorilla"]},{"emoji":"๐Ÿฆง","group":3,"order":3489,"tags":["animal","ape","monkey"],"version":12,"annotation":"orangutan","shortcodes":["orangutan"]},{"emoji":"๐Ÿถ","group":3,"order":3490,"tags":["adorbs","animal","dog","face","pet","puppies","puppy"],"version":0.6,"annotation":"dog face","shortcodes":["dog_face"]},{"emoji":"๐Ÿ•๏ธ","group":3,"order":3491,"tags":["animal","animals","dogs","pet"],"version":0.7,"annotation":"dog","shortcodes":["dog"]},{"emoji":"๐Ÿฆฎ","group":3,"order":3492,"tags":["accessibility","animal","blind","dog","guide"],"version":12,"annotation":"guide dog","shortcodes":["guide_dog"]},{"emoji":"๐Ÿ•โ€๐Ÿฆบ","group":3,"order":3493,"tags":["accessibility","animal","assistance","dog","service"],"version":12,"annotation":"service dog","shortcodes":["service_dog"]},{"emoji":"๐Ÿฉ","group":3,"order":3494,"tags":["animal","dog","fluffy"],"version":0.6,"annotation":"poodle","shortcodes":["poodle"]},{"emoji":"๐Ÿบ","group":3,"order":3495,"tags":["animal","face"],"version":0.6,"annotation":"wolf","shortcodes":["wolf","wolf_face"]},{"emoji":"๐ŸฆŠ","group":3,"order":3496,"tags":["animal","face"],"version":3,"annotation":"fox","shortcodes":["fox","fox_face"]},{"emoji":"๐Ÿฆ","group":3,"order":3497,"tags":["animal","curious","sly"],"version":11,"annotation":"raccoon","shortcodes":["raccoon"]},{"emoji":"๐Ÿฑ","group":3,"order":3498,"tags":["animal","cat","face","kitten","kitty","pet"],"version":0.6,"annotation":"cat face","shortcodes":["cat_face"]},{"emoji":"๐Ÿˆ๏ธ","group":3,"order":3499,"tags":["animal","animals","cats","kitten","pet"],"version":0.7,"annotation":"cat","shortcodes":["cat"]},{"emoji":"๐Ÿˆโ€โฌ›","group":3,"order":3500,"tags":["animal","black","cat","feline","halloween","meow","unlucky"],"version":13,"annotation":"black cat","shortcodes":["black_cat"]},{"emoji":"๐Ÿฆ","group":3,"order":3501,"tags":["alpha","animal","face","leo","mane","order","rawr","roar","safari","strong","zodiac"],"version":1,"annotation":"lion","shortcodes":["lion","lion_face"]},{"emoji":"๐Ÿฏ","group":3,"order":3502,"tags":["animal","big","cat","face","predator","tiger"],"version":0.6,"annotation":"tiger face","shortcodes":["tiger_face"]},{"emoji":"๐Ÿ…","group":3,"order":3503,"tags":["animal","big","cat","predator","zoo"],"version":1,"annotation":"tiger","shortcodes":["tiger"]},{"emoji":"๐Ÿ†","group":3,"order":3504,"tags":["animal","big","cat","predator","zoo"],"version":1,"annotation":"leopard","shortcodes":["leopard"]},{"emoji":"๐Ÿด","group":3,"order":3505,"tags":["animal","dressage","equine","face","farm","horse","horses"],"version":0.6,"annotation":"horse face","shortcodes":["horse_face"]},{"emoji":"๐ŸซŽ","group":3,"order":3506,"tags":["alces","animal","antlers","elk","mammal"],"version":15,"annotation":"moose","shortcodes":["moose"]},{"emoji":"๐Ÿซ","group":3,"order":3507,"tags":["animal","ass","burro","hinny","mammal","mule","stubborn"],"version":15,"annotation":"donkey","shortcodes":["donkey"]},{"emoji":"๐ŸŽ","group":3,"order":3508,"tags":["animal","equestrian","farm","racehorse","racing"],"version":0.6,"annotation":"horse","shortcodes":["horse","racehorse"]},{"emoji":"๐Ÿฆ„","group":3,"order":3509,"tags":["face"],"version":1,"annotation":"unicorn","shortcodes":["unicorn","unicorn_face"]},{"emoji":"๐Ÿฆ“","group":3,"order":3510,"tags":["animal","stripe"],"version":5,"annotation":"zebra","shortcodes":["zebra"]},{"emoji":"๐ŸฆŒ","group":3,"order":3511,"tags":["animal"],"version":3,"annotation":"deer","shortcodes":["deer"]},{"emoji":"๐Ÿฆฌ","group":3,"order":3512,"tags":["animal","buffalo","herd","wisent"],"version":13,"annotation":"bison","shortcodes":["bison"]},{"emoji":"๐Ÿฎ","group":3,"order":3513,"tags":["animal","cow","face","farm","milk","moo"],"version":0.6,"annotation":"cow face","shortcodes":["cow_face"]},{"emoji":"๐Ÿ‚","group":3,"order":3514,"tags":["animal","animals","bull","farm","taurus","zodiac"],"version":1,"annotation":"ox","shortcodes":["ox"]},{"emoji":"๐Ÿƒ","group":3,"order":3515,"tags":["animal","buffalo","water","zoo"],"version":1,"annotation":"water buffalo","shortcodes":["water_buffalo"]},{"emoji":"๐Ÿ„","group":3,"order":3516,"tags":["animal","animals","farm","milk","moo"],"version":1,"annotation":"cow","shortcodes":["cow"]},{"emoji":"๐Ÿท","group":3,"order":3517,"tags":["animal","bacon","face","farm","pig","pork"],"version":0.6,"annotation":"pig face","shortcodes":["pig_face"]},{"emoji":"๐Ÿ–","group":3,"order":3518,"tags":["animal","bacon","farm","pork","sow"],"version":1,"annotation":"pig","shortcodes":["pig"]},{"emoji":"๐Ÿ—","group":3,"order":3519,"tags":["animal","pig"],"version":0.6,"annotation":"boar","shortcodes":["boar"]},{"emoji":"๐Ÿฝ","group":3,"order":3520,"tags":["animal","face","farm","nose","pig","smell","snout"],"version":0.6,"annotation":"pig nose","shortcodes":["pig_nose"]},{"emoji":"๐Ÿ","group":3,"order":3521,"tags":["animal","aries","horns","male","sheep","zodiac","zoo"],"version":1,"annotation":"ram","shortcodes":["ram"]},{"emoji":"๐Ÿ‘","group":3,"order":3522,"tags":["animal","baa","farm","female","fluffy","lamb","sheep","wool"],"version":0.6,"annotation":"ewe","shortcodes":["ewe","sheep"]},{"emoji":"๐Ÿ","group":3,"order":3523,"tags":["animal","capricorn","farm","milk","zodiac"],"version":1,"annotation":"goat","shortcodes":["goat"]},{"emoji":"๐Ÿช","group":3,"order":3524,"tags":["animal","desert","dromedary","hump","one"],"version":1,"annotation":"camel","shortcodes":["dromedary_camel"]},{"emoji":"๐Ÿซ","group":3,"order":3525,"tags":["animal","bactrian","camel","desert","hump","two","two-hump"],"version":0.6,"annotation":"two-hump camel","shortcodes":["camel"]},{"emoji":"๐Ÿฆ™","group":3,"order":3526,"tags":["alpaca","animal","guanaco","vicuรฑa","wool"],"version":11,"annotation":"llama","shortcodes":["llama"]},{"emoji":"๐Ÿฆ’","group":3,"order":3527,"tags":["animal","spots"],"version":5,"annotation":"giraffe","shortcodes":["giraffe"]},{"emoji":"๐Ÿ˜","group":3,"order":3528,"tags":["animal"],"version":0.6,"annotation":"elephant","shortcodes":["elephant"]},{"emoji":"๐Ÿฆฃ","group":3,"order":3529,"tags":["animal","extinction","large","tusk","wooly"],"version":13,"annotation":"mammoth","shortcodes":["mammoth"]},{"emoji":"๐Ÿฆ","group":3,"order":3530,"tags":["animal"],"version":3,"annotation":"rhinoceros","shortcodes":["rhino","rhinoceros"]},{"emoji":"๐Ÿฆ›","group":3,"order":3531,"tags":["animal","hippo"],"version":11,"annotation":"hippopotamus","shortcodes":["hippo"]},{"emoji":"๐Ÿญ","group":3,"order":3532,"tags":["animal","face","mouse"],"version":0.6,"annotation":"mouse face","shortcodes":["mouse_face"]},{"emoji":"๐Ÿ","group":3,"order":3533,"tags":["animal","animals"],"version":1,"annotation":"mouse","shortcodes":["mouse"]},{"emoji":"๐Ÿ€","group":3,"order":3534,"tags":["animal"],"version":1,"annotation":"rat","shortcodes":["rat"]},{"emoji":"๐Ÿน","group":3,"order":3535,"tags":["animal","face","pet"],"version":0.6,"annotation":"hamster","shortcodes":["hamster","hamster_face"]},{"emoji":"๐Ÿฐ","group":3,"order":3536,"tags":["animal","bunny","face","pet","rabbit"],"version":0.6,"annotation":"rabbit face","shortcodes":["rabbit_face"]},{"emoji":"๐Ÿ‡","group":3,"order":3537,"tags":["animal","bunny","pet"],"version":1,"annotation":"rabbit","shortcodes":["rabbit"]},{"emoji":"๐Ÿฟ๏ธ","group":3,"order":3539,"tags":["animal","squirrel"],"version":0.7,"annotation":"chipmunk","shortcodes":["chipmunk"]},{"emoji":"๐Ÿฆซ","group":3,"order":3540,"tags":["animal","dam","teeth"],"version":13,"annotation":"beaver","shortcodes":["beaver"]},{"emoji":"๐Ÿฆ”","group":3,"order":3541,"tags":["animal","spiny"],"version":5,"annotation":"hedgehog","shortcodes":["hedgehog"]},{"emoji":"๐Ÿฆ‡","group":3,"order":3542,"tags":["animal","vampire"],"version":3,"annotation":"bat","shortcodes":["bat"]},{"emoji":"๐Ÿป","group":3,"order":3543,"tags":["animal","face","grizzly","growl","honey"],"version":0.6,"annotation":"bear","shortcodes":["bear","bear_face"]},{"emoji":"๐Ÿปโ€โ„๏ธ","group":3,"order":3544,"tags":["animal","arctic","bear","polar","white"],"version":13,"annotation":"polar bear","shortcodes":["polar_bear","polar_bear_face"]},{"emoji":"๐Ÿจ","group":3,"order":3546,"tags":["animal","australia","bear","down","face","marsupial","under"],"version":0.6,"annotation":"koala","shortcodes":["koala","koala_face"]},{"emoji":"๐Ÿผ","group":3,"order":3547,"tags":["animal","bamboo","face"],"version":0.6,"annotation":"panda","shortcodes":["panda","panda_face"]},{"emoji":"๐Ÿฆฅ","group":3,"order":3548,"tags":["lazy","slow"],"version":12,"annotation":"sloth","shortcodes":["sloth"]},{"emoji":"๐Ÿฆฆ","group":3,"order":3549,"tags":["animal","fishing","playful"],"version":12,"annotation":"otter","shortcodes":["otter"]},{"emoji":"๐Ÿฆจ","group":3,"order":3550,"tags":["animal","stink"],"version":12,"annotation":"skunk","shortcodes":["skunk"]},{"emoji":"๐Ÿฆ˜","group":3,"order":3551,"tags":["animal","joey","jump","marsupial"],"version":11,"annotation":"kangaroo","shortcodes":["kangaroo"]},{"emoji":"๐Ÿฆก","group":3,"order":3552,"tags":["animal","honey","pester"],"version":11,"annotation":"badger","shortcodes":["badger"]},{"emoji":"๐Ÿพ","group":3,"order":3553,"tags":["feet","paw","paws","print","prints"],"version":0.6,"annotation":"paw prints","shortcodes":["paw_prints"]},{"emoji":"๐Ÿฆƒ","group":3,"order":3554,"tags":["bird","gobble","thanksgiving"],"version":1,"annotation":"turkey","shortcodes":["turkey"]},{"emoji":"๐Ÿ”","group":3,"order":3555,"tags":["animal","bird","ornithology"],"version":0.6,"annotation":"chicken","shortcodes":["chicken","chicken_face"]},{"emoji":"๐Ÿ“","group":3,"order":3556,"tags":["animal","bird","ornithology"],"version":1,"annotation":"rooster","shortcodes":["rooster"]},{"emoji":"๐Ÿฃ","group":3,"order":3557,"tags":["animal","baby","bird","chick","egg","hatching"],"version":0.6,"annotation":"hatching chick","shortcodes":["hatching_chick"]},{"emoji":"๐Ÿค","group":3,"order":3558,"tags":["animal","baby","bird","chick","ornithology"],"version":0.6,"annotation":"baby chick","shortcodes":["baby_chick"]},{"emoji":"๐Ÿฅ","group":3,"order":3559,"tags":["animal","baby","bird","chick","front-facing","newborn","ornithology"],"version":0.6,"annotation":"front-facing baby chick","shortcodes":["hatched_chick"]},{"emoji":"๐Ÿฆ๏ธ","group":3,"order":3560,"tags":["animal","ornithology"],"version":0.6,"annotation":"bird","shortcodes":["bird","bird_face"]},{"emoji":"๐Ÿง","group":3,"order":3561,"tags":["animal","antarctica","bird","ornithology"],"version":0.6,"annotation":"penguin","shortcodes":["penguin","penguin_face"]},{"emoji":"๐Ÿ•Š๏ธ","group":3,"order":3563,"tags":["bird","fly","ornithology","peace"],"version":0.7,"annotation":"dove","shortcodes":["dove"]},{"emoji":"๐Ÿฆ…","group":3,"order":3564,"tags":["animal","bird","ornithology"],"version":3,"annotation":"eagle","shortcodes":["eagle"]},{"emoji":"๐Ÿฆ†","group":3,"order":3565,"tags":["animal","bird","ornithology"],"version":3,"annotation":"duck","shortcodes":["duck"]},{"emoji":"๐Ÿฆข","group":3,"order":3566,"tags":["animal","bird","cygnet","duckling","ornithology","ugly"],"version":11,"annotation":"swan","shortcodes":["swan"]},{"emoji":"๐Ÿฆ‰","group":3,"order":3567,"tags":["animal","bird","ornithology","wise"],"version":3,"annotation":"owl","shortcodes":["owl"]},{"emoji":"๐Ÿฆค","group":3,"order":3568,"tags":["animal","bird","extinction","large","ornithology"],"version":13,"annotation":"dodo","shortcodes":["dodo"]},{"emoji":"๐Ÿชถ","group":3,"order":3569,"tags":["bird","flight","light","plumage"],"version":13,"annotation":"feather","shortcodes":["feather"]},{"emoji":"๐Ÿฆฉ","group":3,"order":3570,"tags":["animal","bird","flamboyant","ornithology","tropical"],"version":12,"annotation":"flamingo","shortcodes":["flamingo"]},{"emoji":"๐Ÿฆš","group":3,"order":3571,"tags":["animal","bird","colorful","ornithology","ostentatious","peahen","pretty","proud"],"version":11,"annotation":"peacock","shortcodes":["peacock"]},{"emoji":"๐Ÿฆœ","group":3,"order":3572,"tags":["animal","bird","ornithology","pirate","talk"],"version":11,"annotation":"parrot","shortcodes":["parrot"]},{"emoji":"๐Ÿชฝ","group":3,"order":3573,"tags":["angelic","ascend","aviation","bird","fly","flying","heavenly","mythology","soar"],"version":15,"annotation":"wing","shortcodes":["wing"]},{"emoji":"๐Ÿฆโ€โฌ›","group":3,"order":3574,"tags":["animal","beak","bird","black","caw","corvid","crow","ornithology","raven","rook"],"version":15,"annotation":"black bird","shortcodes":["black_bird"]},{"emoji":"๐Ÿชฟ","group":3,"order":3575,"tags":["animal","bird","duck","flock","fowl","gaggle","gander","geese","honk","ornithology","silly"],"version":15,"annotation":"goose","shortcodes":["goose"]},{"emoji":"๐Ÿฆโ€๐Ÿ”ฅ","group":3,"order":3576,"tags":["ascend","ascension","emerge","fantasy","firebird","glory","immortal","rebirth","reincarnation","reinvent","renewal","revival","revive","rise","transform"],"version":15.1,"annotation":"phoenix","shortcodes":["phoenix"]},{"emoji":"๐Ÿธ","group":3,"order":3577,"tags":["animal","face"],"version":0.6,"annotation":"frog","shortcodes":["frog","frog_face"]},{"emoji":"๐ŸŠ","group":3,"order":3578,"tags":["animal","zoo"],"version":1,"annotation":"crocodile","shortcodes":["crocodile"]},{"emoji":"๐Ÿข","group":3,"order":3579,"tags":["animal","terrapin","tortoise"],"version":0.6,"annotation":"turtle","shortcodes":["turtle"]},{"emoji":"๐ŸฆŽ","group":3,"order":3580,"tags":["animal","reptile"],"version":3,"annotation":"lizard","shortcodes":["lizard"]},{"emoji":"๐Ÿ","group":3,"order":3581,"tags":["animal","bearer","ophiuchus","serpent","zodiac"],"version":0.6,"annotation":"snake","shortcodes":["snake"]},{"emoji":"๐Ÿฒ","group":3,"order":3582,"tags":["animal","dragon","face","fairy","fairytale","tale"],"version":0.6,"annotation":"dragon face","shortcodes":["dragon_face"]},{"emoji":"๐Ÿ‰","group":3,"order":3583,"tags":["animal","fairy","fairytale","knights","tale"],"version":1,"annotation":"dragon","shortcodes":["dragon"]},{"emoji":"๐Ÿฆ•","group":3,"order":3584,"tags":["brachiosaurus","brontosaurus","dinosaur","diplodocus"],"version":5,"annotation":"sauropod","shortcodes":["sauropod"]},{"emoji":"๐Ÿฆ–","group":3,"order":3585,"tags":["dinosaur","rex","t","t-rex","tyrannosaurus"],"version":5,"annotation":"T-Rex","shortcodes":["t-rex","trex"]},{"emoji":"๐Ÿณ","group":3,"order":3586,"tags":["animal","beach","face","ocean","spouting","whale"],"version":0.6,"annotation":"spouting whale","shortcodes":["spouting_whale"]},{"emoji":"๐Ÿ‹","group":3,"order":3587,"tags":["animal","beach","ocean"],"version":1,"annotation":"whale","shortcodes":["whale"]},{"emoji":"๐Ÿฌ","group":3,"order":3588,"tags":["animal","beach","flipper","ocean"],"version":0.6,"annotation":"dolphin","shortcodes":["dolphin"]},{"emoji":"๐Ÿฆญ","group":3,"order":3589,"tags":["animal","lion","ocean","sea"],"version":13,"annotation":"seal","shortcodes":["seal"]},{"emoji":"๐ŸŸ๏ธ","group":3,"order":3590,"tags":["animal","dinner","fishes","fishing","pisces","zodiac"],"version":0.6,"annotation":"fish","shortcodes":["fish"]},{"emoji":"๐Ÿ ","group":3,"order":3591,"tags":["animal","fish","fishes","tropical"],"version":0.6,"annotation":"tropical fish","shortcodes":["tropical_fish"]},{"emoji":"๐Ÿก","group":3,"order":3592,"tags":["animal","fish"],"version":0.6,"annotation":"blowfish","shortcodes":["blowfish"]},{"emoji":"๐Ÿฆˆ","group":3,"order":3593,"tags":["animal","fish"],"version":3,"annotation":"shark","shortcodes":["shark"]},{"emoji":"๐Ÿ™","group":3,"order":3594,"tags":["animal","creature","ocean"],"version":0.6,"annotation":"octopus","shortcodes":["octopus"]},{"emoji":"๐Ÿš","group":3,"order":3595,"tags":["animal","beach","conch","sea","shell","spiral"],"version":0.6,"annotation":"spiral shell","shortcodes":["shell"]},{"emoji":"๐Ÿชธ","group":3,"order":3596,"tags":["change","climate","ocean","reef","sea"],"version":14,"annotation":"coral","shortcodes":["coral"]},{"emoji":"๐Ÿชผ","group":3,"order":3597,"tags":["animal","aquarium","burn","invertebrate","jelly","life","marine","ocean","ouch","plankton","sea","sting","stinger","tentacles"],"version":15,"annotation":"jellyfish","shortcodes":["jellyfish"]},{"emoji":"๐Ÿฆ€","group":3,"order":3598,"tags":["cancer","zodiac"],"version":1,"annotation":"crab","shortcodes":["crab"]},{"emoji":"๐Ÿฆž","group":3,"order":3599,"tags":["animal","bisque","claws","seafood"],"version":11,"annotation":"lobster","shortcodes":["lobster"]},{"emoji":"๐Ÿฆ","group":3,"order":3600,"tags":["food","shellfish","small"],"version":3,"annotation":"shrimp","shortcodes":["shrimp"]},{"emoji":"๐Ÿฆ‘","group":3,"order":3601,"tags":["animal","food","mollusk"],"version":3,"annotation":"squid","shortcodes":["squid"]},{"emoji":"๐Ÿฆช","group":3,"order":3602,"tags":["diving","pearl"],"version":12,"annotation":"oyster","shortcodes":["oyster"]},{"emoji":"๐ŸŒ","group":3,"order":3603,"tags":["animal","escargot","garden","nature","slug"],"version":0.6,"annotation":"snail","shortcodes":["snail"]},{"emoji":"๐Ÿฆ‹","group":3,"order":3604,"tags":["insect","pretty"],"version":3,"annotation":"butterfly","shortcodes":["butterfly"]},{"emoji":"๐Ÿ›","group":3,"order":3605,"tags":["animal","garden","insect"],"version":0.6,"annotation":"bug","shortcodes":["bug"]},{"emoji":"๐Ÿœ","group":3,"order":3606,"tags":["animal","garden","insect"],"version":0.6,"annotation":"ant","shortcodes":["ant"]},{"emoji":"๐Ÿ","group":3,"order":3607,"tags":["animal","bee","bumblebee","honey","insect","nature","spring"],"version":0.6,"annotation":"honeybee","shortcodes":["bee"]},{"emoji":"๐Ÿชฒ","group":3,"order":3608,"tags":["animal","bug","insect"],"version":13,"annotation":"beetle","shortcodes":["beetle"]},{"emoji":"๐Ÿž","group":3,"order":3609,"tags":["animal","beetle","garden","insect","lady","ladybird","ladybug","nature"],"version":0.6,"annotation":"lady beetle","shortcodes":["lady_beetle"]},{"emoji":"๐Ÿฆ—","group":3,"order":3610,"tags":["animal","bug","grasshopper","insect","orthoptera"],"version":5,"annotation":"cricket","shortcodes":["cricket"]},{"emoji":"๐Ÿชณ","group":3,"order":3611,"tags":["animal","insect","pest","roach"],"version":13,"annotation":"cockroach","shortcodes":["cockroach"]},{"emoji":"๐Ÿ•ท๏ธ","group":3,"order":3613,"tags":["animal","insect"],"version":0.7,"annotation":"spider","shortcodes":["spider"]},{"emoji":"๐Ÿ•ธ๏ธ","group":3,"order":3615,"tags":["spider","web"],"version":0.7,"annotation":"spider web","shortcodes":["spider_web"]},{"emoji":"๐Ÿฆ‚","group":3,"order":3616,"tags":["scorpio","scorpius","zodiac"],"version":1,"annotation":"scorpion","shortcodes":["scorpion"]},{"emoji":"๐ŸฆŸ","group":3,"order":3617,"tags":["bite","disease","fever","insect","malaria","pest","virus"],"version":11,"annotation":"mosquito","shortcodes":["mosquito"]},{"emoji":"๐Ÿชฐ","group":3,"order":3618,"tags":["animal","disease","insect","maggot","pest","rotting"],"version":13,"annotation":"fly","shortcodes":["fly"]},{"emoji":"๐Ÿชฑ","group":3,"order":3619,"tags":["animal","annelid","earthworm","parasite"],"version":13,"annotation":"worm","shortcodes":["worm"]},{"emoji":"๐Ÿฆ ","group":3,"order":3620,"tags":["amoeba","bacteria","science","virus"],"version":11,"annotation":"microbe","shortcodes":["microbe"]},{"emoji":"๐Ÿ’","group":3,"order":3621,"tags":["anniversary","birthday","date","flower","love","plant","romance"],"version":0.6,"annotation":"bouquet","shortcodes":["bouquet"]},{"emoji":"๐ŸŒธ","group":3,"order":3622,"tags":["blossom","cherry","flower","plant","spring","springtime"],"version":0.6,"annotation":"cherry blossom","shortcodes":["cherry_blossom"]},{"emoji":"๐Ÿ’ฎ","group":3,"order":3623,"tags":["flower","white"],"version":0.6,"annotation":"white flower","shortcodes":["white_flower"]},{"emoji":"๐Ÿชท","group":3,"order":3624,"tags":["beauty","buddhism","calm","flower","hinduism","peace","purity","serenity"],"version":14,"annotation":"lotus","shortcodes":["lotus"]},{"emoji":"๐Ÿต๏ธ","group":3,"order":3626,"tags":["plant"],"version":0.7,"annotation":"rosette","shortcodes":["rosette"]},{"emoji":"๐ŸŒน","group":3,"order":3627,"tags":["beauty","elegant","flower","love","plant","red","valentine"],"version":0.6,"annotation":"rose","shortcodes":["rose"]},{"emoji":"๐Ÿฅ€","group":3,"order":3628,"tags":["dying","flower","wilted"],"version":3,"annotation":"wilted flower","shortcodes":["wilted_flower"]},{"emoji":"๐ŸŒบ","group":3,"order":3629,"tags":["flower","plant"],"version":0.6,"annotation":"hibiscus","shortcodes":["hibiscus"]},{"emoji":"๐ŸŒป","group":3,"order":3630,"tags":["flower","outdoors","plant","sun"],"version":0.6,"annotation":"sunflower","shortcodes":["sunflower"]},{"emoji":"๐ŸŒผ","group":3,"order":3631,"tags":["buttercup","dandelion","flower","plant"],"version":0.6,"annotation":"blossom","shortcodes":["blossom"]},{"emoji":"๐ŸŒท","group":3,"order":3632,"tags":["blossom","flower","growth","plant"],"version":0.6,"annotation":"tulip","shortcodes":["tulip"]},{"emoji":"๐Ÿชป","group":3,"order":3633,"tags":["bloom","bluebonnet","flower","indigo","lavender","lilac","lupine","plant","purple","shrub","snapdragon","spring","violet"],"version":15,"annotation":"hyacinth","shortcodes":["hyacinth"]},{"emoji":"๐ŸŒฑ","group":3,"order":3634,"tags":["plant","sapling","sprout","young"],"version":0.6,"annotation":"seedling","shortcodes":["seedling"]},{"emoji":"๐Ÿชด","group":3,"order":3635,"tags":["decor","grow","house","nurturing","plant","pot","potted"],"version":13,"annotation":"potted plant","shortcodes":["potted_plant"]},{"emoji":"๐ŸŒฒ","group":3,"order":3636,"tags":["christmas","evergreen","forest","pine","tree"],"version":1,"annotation":"evergreen tree","shortcodes":["evergreen_tree"]},{"emoji":"๐ŸŒณ","group":3,"order":3637,"tags":["deciduous","forest","green","habitat","shedding","tree"],"version":1,"annotation":"deciduous tree","shortcodes":["deciduous_tree"]},{"emoji":"๐ŸŒด","group":3,"order":3638,"tags":["beach","palm","plant","tree","tropical"],"version":0.6,"annotation":"palm tree","shortcodes":["palm_tree"]},{"emoji":"๐ŸŒต","group":3,"order":3639,"tags":["desert","drought","nature","plant"],"version":0.6,"annotation":"cactus","shortcodes":["cactus"]},{"emoji":"๐ŸŒพ","group":3,"order":3640,"tags":["ear","grain","grains","plant","rice","sheaf"],"version":0.6,"annotation":"sheaf of rice","shortcodes":["ear_of_rice","sheaf_of_rice"]},{"emoji":"๐ŸŒฟ","group":3,"order":3641,"tags":["leaf","plant"],"version":0.6,"annotation":"herb","shortcodes":["herb"]},{"emoji":"โ˜˜๏ธ","group":3,"order":3643,"tags":["irish","plant"],"version":1,"annotation":"shamrock","shortcodes":["shamrock"]},{"emoji":"๐Ÿ€","group":3,"order":3644,"tags":["4","clover","four","four-leaf","irish","leaf","lucky","plant"],"version":0.6,"annotation":"four leaf clover","shortcodes":["four_leaf_clover"]},{"emoji":"๐Ÿ","group":3,"order":3645,"tags":["falling","leaf","maple"],"version":0.6,"annotation":"maple leaf","shortcodes":["maple_leaf"]},{"emoji":"๐Ÿ‚","group":3,"order":3646,"tags":["autumn","fall","fallen","falling","leaf"],"version":0.6,"annotation":"fallen leaf","shortcodes":["fallen_leaf"]},{"emoji":"๐Ÿƒ","group":3,"order":3647,"tags":["blow","flutter","fluttering","leaf","wind"],"version":0.6,"annotation":"leaf fluttering in wind","shortcodes":["leaves"]},{"emoji":"๐Ÿชน","group":3,"order":3648,"tags":["branch","empty","home","nest","nesting"],"version":14,"annotation":"empty nest","shortcodes":["empty_nest","nest"]},{"emoji":"๐Ÿชบ","group":3,"order":3649,"tags":["bird","branch","egg","eggs","nest","nesting"],"version":14,"annotation":"nest with eggs","shortcodes":["nest_with_eggs"]},{"emoji":"๐Ÿ„","group":3,"order":3650,"tags":["fungus","toadstool"],"version":0.6,"annotation":"mushroom","shortcodes":["mushroom"]},{"emoji":"๐Ÿชพ","group":3,"order":3651,"tags":["bare","barren","branches","dead","drought","leafless","tree","trunk","winter","wood"],"version":16,"annotation":"leafless tree","shortcodes":["leafless_tree"]},{"emoji":"๐Ÿ‡","group":4,"order":3652,"tags":["dionysus","fruit","grape"],"version":0.6,"annotation":"grapes","shortcodes":["grapes"]},{"emoji":"๐Ÿˆ","group":4,"order":3653,"tags":["cantaloupe","fruit"],"version":0.6,"annotation":"melon","shortcodes":["melon"]},{"emoji":"๐Ÿ‰","group":4,"order":3654,"tags":["fruit"],"version":0.6,"annotation":"watermelon","shortcodes":["watermelon"]},{"emoji":"๐ŸŠ","group":4,"order":3655,"tags":["c","citrus","fruit","nectarine","orange","vitamin"],"version":0.6,"annotation":"tangerine","shortcodes":["orange","tangerine"]},{"emoji":"๐Ÿ‹","group":4,"order":3656,"tags":["citrus","fruit","sour"],"version":1,"annotation":"lemon","shortcodes":["lemon"]},{"emoji":"๐Ÿ‹โ€๐ŸŸฉ","group":4,"order":3657,"tags":["acidity","citrus","cocktail","fruit","garnish","key","margarita","mojito","refreshing","salsa","sour","tangy","tequila","tropical","zest"],"version":15.1,"annotation":"lime","shortcodes":["lime"]},{"emoji":"๐ŸŒ","group":4,"order":3658,"tags":["fruit","potassium"],"version":0.6,"annotation":"banana","shortcodes":["banana"]},{"emoji":"๐Ÿ","group":4,"order":3659,"tags":["colada","fruit","pina","tropical"],"version":0.6,"annotation":"pineapple","shortcodes":["pineapple"]},{"emoji":"๐Ÿฅญ","group":4,"order":3660,"tags":["food","fruit","tropical"],"version":11,"annotation":"mango","shortcodes":["mango"]},{"emoji":"๐ŸŽ","group":4,"order":3661,"tags":["apple","diet","food","fruit","health","red","ripe"],"version":0.6,"annotation":"red apple","shortcodes":["apple","red_apple"]},{"emoji":"๐Ÿ","group":4,"order":3662,"tags":["apple","fruit","green"],"version":0.6,"annotation":"green apple","shortcodes":["green_apple"]},{"emoji":"๐Ÿ","group":4,"order":3663,"tags":["fruit"],"version":1,"annotation":"pear","shortcodes":["pear"]},{"emoji":"๐Ÿ‘","group":4,"order":3664,"tags":["fruit"],"version":0.6,"annotation":"peach","shortcodes":["peach"]},{"emoji":"๐Ÿ’","group":4,"order":3665,"tags":["berries","cherry","fruit","red"],"version":0.6,"annotation":"cherries","shortcodes":["cherries"]},{"emoji":"๐Ÿ“","group":4,"order":3666,"tags":["berry","fruit"],"version":0.6,"annotation":"strawberry","shortcodes":["strawberry"]},{"emoji":"๐Ÿซ","group":4,"order":3667,"tags":["berries","berry","bilberry","blue","blueberry","food","fruit"],"version":13,"annotation":"blueberries","shortcodes":["blueberries"]},{"emoji":"๐Ÿฅ","group":4,"order":3668,"tags":["food","fruit","kiwi"],"version":3,"annotation":"kiwi fruit","shortcodes":["kiwi"]},{"emoji":"๐Ÿ…","group":4,"order":3669,"tags":["food","fruit","vegetable"],"version":0.6,"annotation":"tomato","shortcodes":["tomato"]},{"emoji":"๐Ÿซ’","group":4,"order":3670,"tags":["food"],"version":13,"annotation":"olive","shortcodes":["olive"]},{"emoji":"๐Ÿฅฅ","group":4,"order":3671,"tags":["colada","palm","piรฑa"],"version":5,"annotation":"coconut","shortcodes":["coconut"]},{"emoji":"๐Ÿฅ‘","group":4,"order":3672,"tags":["food","fruit"],"version":3,"annotation":"avocado","shortcodes":["avocado"]},{"emoji":"๐Ÿ†","group":4,"order":3673,"tags":["aubergine","vegetable"],"version":0.6,"annotation":"eggplant","shortcodes":["eggplant"]},{"emoji":"๐Ÿฅ”","group":4,"order":3674,"tags":["food","vegetable"],"version":3,"annotation":"potato","shortcodes":["potato"]},{"emoji":"๐Ÿฅ•","group":4,"order":3675,"tags":["food","vegetable"],"version":3,"annotation":"carrot","shortcodes":["carrot"]},{"emoji":"๐ŸŒฝ","group":4,"order":3676,"tags":["corn","crops","ear","farm","maize","maze"],"version":0.6,"annotation":"ear of corn","shortcodes":["corn","ear_of_corn"]},{"emoji":"๐ŸŒถ๏ธ","group":4,"order":3678,"tags":["hot","pepper"],"version":0.7,"annotation":"hot pepper","shortcodes":["hot_pepper"]},{"emoji":"๐Ÿซ‘","group":4,"order":3679,"tags":["bell","capsicum","food","pepper","vegetable"],"version":13,"annotation":"bell pepper","shortcodes":["bell_pepper"]},{"emoji":"๐Ÿฅ’","group":4,"order":3680,"tags":["food","pickle","vegetable"],"version":3,"annotation":"cucumber","shortcodes":["cucumber"]},{"emoji":"๐Ÿฅฌ","group":4,"order":3681,"tags":["bok","burgers","cabbage","choy","green","kale","leafy","lettuce","salad"],"version":11,"annotation":"leafy green","shortcodes":["leafy_green"]},{"emoji":"๐Ÿฅฆ","group":4,"order":3682,"tags":["cabbage","wild"],"version":5,"annotation":"broccoli","shortcodes":["broccoli"]},{"emoji":"๐Ÿง„","group":4,"order":3683,"tags":["flavoring"],"version":12,"annotation":"garlic","shortcodes":["garlic"]},{"emoji":"๐Ÿง…","group":4,"order":3684,"tags":["flavoring"],"version":12,"annotation":"onion","shortcodes":["onion"]},{"emoji":"๐Ÿฅœ","group":4,"order":3685,"tags":["food","nut","peanut","vegetable"],"version":3,"annotation":"peanuts","shortcodes":["peanuts"]},{"emoji":"๐Ÿซ˜","group":4,"order":3686,"tags":["food","kidney","legume","small"],"version":14,"annotation":"beans","shortcodes":["beans"]},{"emoji":"๐ŸŒฐ","group":4,"order":3687,"tags":["almond","plant"],"version":0.6,"annotation":"chestnut","shortcodes":["chestnut"]},{"emoji":"๐Ÿซš","group":4,"order":3688,"tags":["beer","ginger","health","herb","natural","root","spice"],"version":15,"annotation":"ginger root","shortcodes":["ginger"]},{"emoji":"๐Ÿซ›","group":4,"order":3689,"tags":["beans","beanstalk","edamame","legume","pea","pod","soybean","vegetable","veggie"],"version":15,"annotation":"pea pod","shortcodes":["pea"]},{"emoji":"๐Ÿ„โ€๐ŸŸซ","group":4,"order":3690,"tags":["food","fungi","fungus","mushroom","nature","pizza","portobello","shiitake","shroom","spore","sprout","toppings","truffle","vegetable","vegetarian","veggie"],"version":15.1,"annotation":"brown mushroom","shortcodes":["brown_mushroom"]},{"emoji":"๐Ÿซœ","group":4,"order":3691,"tags":["beet","food","garden","radish","root","salad","turnip","vegetable","vegetarian"],"version":16,"annotation":"root vegetable","shortcodes":["root_vegetable"]},{"emoji":"๐Ÿž","group":4,"order":3692,"tags":["carbs","food","grain","loaf","restaurant","toast","wheat"],"version":0.6,"annotation":"bread","shortcodes":["bread"]},{"emoji":"๐Ÿฅ","group":4,"order":3693,"tags":["bread","breakfast","crescent","food","french","roll"],"version":3,"annotation":"croissant","shortcodes":["croissant"]},{"emoji":"๐Ÿฅ–","group":4,"order":3694,"tags":["baguette","bread","food","french"],"version":3,"annotation":"baguette bread","shortcodes":["baguette_bread"]},{"emoji":"๐Ÿซ“","group":4,"order":3695,"tags":["arepa","bread","food","gordita","lavash","naan","pita"],"version":13,"annotation":"flatbread","shortcodes":["flatbread"]},{"emoji":"๐Ÿฅจ","group":4,"order":3696,"tags":["convoluted","twisted"],"version":5,"annotation":"pretzel","shortcodes":["pretzel"]},{"emoji":"๐Ÿฅฏ","group":4,"order":3697,"tags":["bakery","bread","breakfast","schmear"],"version":11,"annotation":"bagel","shortcodes":["bagel"]},{"emoji":"๐Ÿฅž","group":4,"order":3698,"tags":["breakfast","crรชpe","food","hotcake","pancake"],"version":3,"annotation":"pancakes","shortcodes":["pancakes"]},{"emoji":"๐Ÿง‡","group":4,"order":3699,"tags":["breakfast","indecisive","iron"],"version":12,"annotation":"waffle","shortcodes":["waffle"]},{"emoji":"๐Ÿง€","group":4,"order":3700,"tags":["cheese","wedge"],"version":1,"annotation":"cheese wedge","shortcodes":["cheese"]},{"emoji":"๐Ÿ–","group":4,"order":3701,"tags":["bone","meat"],"version":0.6,"annotation":"meat on bone","shortcodes":["meat_on_bone"]},{"emoji":"๐Ÿ—","group":4,"order":3702,"tags":["bone","chicken","drumstick","hungry","leg","poultry","turkey"],"version":0.6,"annotation":"poultry leg","shortcodes":["poultry_leg"]},{"emoji":"๐Ÿฅฉ","group":4,"order":3703,"tags":["chop","cut","lambchop","meat","porkchop","red","steak"],"version":5,"annotation":"cut of meat","shortcodes":["cut_of_meat"]},{"emoji":"๐Ÿฅ“","group":4,"order":3704,"tags":["breakfast","food","meat"],"version":3,"annotation":"bacon","shortcodes":["bacon"]},{"emoji":"๐Ÿ”","group":4,"order":3705,"tags":["burger","eat","fast","food","hungry"],"version":0.6,"annotation":"hamburger","shortcodes":["hamburger"]},{"emoji":"๐ŸŸ","group":4,"order":3706,"tags":["fast","food","french","fries"],"version":0.6,"annotation":"french fries","shortcodes":["french_fries","fries"]},{"emoji":"๐Ÿ•","group":4,"order":3707,"tags":["cheese","food","hungry","pepperoni","slice"],"version":0.6,"annotation":"pizza","shortcodes":["pizza"]},{"emoji":"๐ŸŒญ","group":4,"order":3708,"tags":["dog","frankfurter","hot","hotdog","sausage"],"version":1,"annotation":"hot dog","shortcodes":["hotdog"]},{"emoji":"๐Ÿฅช","group":4,"order":3709,"tags":["bread"],"version":5,"annotation":"sandwich","shortcodes":["sandwich"]},{"emoji":"๐ŸŒฎ","group":4,"order":3710,"tags":["mexican"],"version":1,"annotation":"taco","shortcodes":["taco"]},{"emoji":"๐ŸŒฏ","group":4,"order":3711,"tags":["mexican","wrap"],"version":1,"annotation":"burrito","shortcodes":["burrito"]},{"emoji":"๐Ÿซ”","group":4,"order":3712,"tags":["food","mexican","pamonha","wrapped"],"version":13,"annotation":"tamale","shortcodes":["tamale"]},{"emoji":"๐Ÿฅ™","group":4,"order":3713,"tags":["falafel","flatbread","food","gyro","kebab","stuffed"],"version":3,"annotation":"stuffed flatbread","shortcodes":["stuffed_flatbread"]},{"emoji":"๐Ÿง†","group":4,"order":3714,"tags":["chickpea","meatball"],"version":12,"annotation":"falafel","shortcodes":["falafel"]},{"emoji":"๐Ÿฅš","group":4,"order":3715,"tags":["breakfast","food"],"version":3,"annotation":"egg","shortcodes":["egg"]},{"emoji":"๐Ÿณ","group":4,"order":3716,"tags":["breakfast","easy","egg","fry","frying","over","pan","restaurant","side","sunny","up"],"version":0.6,"annotation":"cooking","shortcodes":["cooking","fried_egg"]},{"emoji":"๐Ÿฅ˜","group":4,"order":3717,"tags":["casserole","food","paella","pan","shallow"],"version":3,"annotation":"shallow pan of food","shortcodes":["shallow_pan_of_food"]},{"emoji":"๐Ÿฒ","group":4,"order":3718,"tags":["food","pot","soup","stew"],"version":0.6,"annotation":"pot of food","shortcodes":["pot_of_food","stew"]},{"emoji":"๐Ÿซ•","group":4,"order":3719,"tags":["cheese","chocolate","food","melted","pot","ski"],"version":13,"annotation":"fondue","shortcodes":["fondue"]},{"emoji":"๐Ÿฅฃ","group":4,"order":3720,"tags":["bowl","breakfast","cereal","congee","oatmeal","porridge","spoon"],"version":5,"annotation":"bowl with spoon","shortcodes":["bowl_with_spoon"]},{"emoji":"๐Ÿฅ—","group":4,"order":3721,"tags":["food","green","salad"],"version":3,"annotation":"green salad","shortcodes":["green_salad","salad"]},{"emoji":"๐Ÿฟ","group":4,"order":3722,"tags":["corn","movie","pop"],"version":1,"annotation":"popcorn","shortcodes":["popcorn"]},{"emoji":"๐Ÿงˆ","group":4,"order":3723,"tags":["dairy"],"version":12,"annotation":"butter","shortcodes":["butter"]},{"emoji":"๐Ÿง‚","group":4,"order":3724,"tags":["condiment","flavor","mad","salty","shaker","taste","upset"],"version":11,"annotation":"salt","shortcodes":["salt"]},{"emoji":"๐Ÿฅซ","group":4,"order":3725,"tags":["can","canned","food"],"version":5,"annotation":"canned food","shortcodes":["canned_food"]},{"emoji":"๐Ÿฑ","group":4,"order":3726,"tags":["bento","box","food"],"version":0.6,"annotation":"bento box","shortcodes":["bento","bento_box"]},{"emoji":"๐Ÿ˜","group":4,"order":3727,"tags":["cracker","food","rice"],"version":0.6,"annotation":"rice cracker","shortcodes":["rice_cracker"]},{"emoji":"๐Ÿ™","group":4,"order":3728,"tags":["ball","food","japanese","rice"],"version":0.6,"annotation":"rice ball","shortcodes":["rice_ball"]},{"emoji":"๐Ÿš","group":4,"order":3729,"tags":["cooked","food","rice"],"version":0.6,"annotation":"cooked rice","shortcodes":["cooked_rice","rice"]},{"emoji":"๐Ÿ›","group":4,"order":3730,"tags":["curry","food","rice"],"version":0.6,"annotation":"curry rice","shortcodes":["curry","curry_rice"]},{"emoji":"๐Ÿœ","group":4,"order":3731,"tags":["bowl","chopsticks","food","noodle","pho","ramen","soup","steaming"],"version":0.6,"annotation":"steaming bowl","shortcodes":["ramen","steaming_bowl"]},{"emoji":"๐Ÿ","group":4,"order":3732,"tags":["food","meatballs","pasta","restaurant"],"version":0.6,"annotation":"spaghetti","shortcodes":["spaghetti"]},{"emoji":"๐Ÿ ","group":4,"order":3733,"tags":["food","potato","roasted","sweet"],"version":0.6,"annotation":"roasted sweet potato","shortcodes":["sweet_potato"]},{"emoji":"๐Ÿข","group":4,"order":3734,"tags":["food","kebab","restaurant","seafood","skewer","stick"],"version":0.6,"annotation":"oden","shortcodes":["oden"]},{"emoji":"๐Ÿฃ","group":4,"order":3735,"tags":["food"],"version":0.6,"annotation":"sushi","shortcodes":["sushi"]},{"emoji":"๐Ÿค","group":4,"order":3736,"tags":["fried","prawn","shrimp","tempura"],"version":0.6,"annotation":"fried shrimp","shortcodes":["fried_shrimp"]},{"emoji":"๐Ÿฅ","group":4,"order":3737,"tags":["cake","fish","food","pastry","restaurant","swirl"],"version":0.6,"annotation":"fish cake with swirl","shortcodes":["fish_cake"]},{"emoji":"๐Ÿฅฎ","group":4,"order":3738,"tags":["autumn","cake","festival","moon","yuรจbวng"],"version":11,"annotation":"moon cake","shortcodes":["moon_cake"]},{"emoji":"๐Ÿก","group":4,"order":3739,"tags":["dessert","japanese","skewer","stick","sweet"],"version":0.6,"annotation":"dango","shortcodes":["dango"]},{"emoji":"๐ŸฅŸ","group":4,"order":3740,"tags":["empanada","gyลza","jiaozi","pierogi","potsticker"],"version":5,"annotation":"dumpling","shortcodes":["dumpling"]},{"emoji":"๐Ÿฅ ","group":4,"order":3741,"tags":["cookie","fortune","prophecy"],"version":5,"annotation":"fortune cookie","shortcodes":["fortune_cookie"]},{"emoji":"๐Ÿฅก","group":4,"order":3742,"tags":["box","chopsticks","delivery","food","oyster","pail","takeout"],"version":5,"annotation":"takeout box","shortcodes":["takeout_box"]},{"emoji":"๐Ÿฆ","group":4,"order":3743,"tags":["cream","dessert","food","ice","icecream","restaurant","serve","soft","sweet"],"version":0.6,"annotation":"soft ice cream","shortcodes":["icecream","soft_serve"]},{"emoji":"๐Ÿง","group":4,"order":3744,"tags":["dessert","ice","restaurant","shaved","sweet"],"version":0.6,"annotation":"shaved ice","shortcodes":["shaved_ice"]},{"emoji":"๐Ÿจ","group":4,"order":3745,"tags":["cream","dessert","food","ice","restaurant","sweet"],"version":0.6,"annotation":"ice cream","shortcodes":["ice_cream"]},{"emoji":"๐Ÿฉ","group":4,"order":3746,"tags":["breakfast","dessert","donut","food","sweet"],"version":0.6,"annotation":"doughnut","shortcodes":["doughnut"]},{"emoji":"๐Ÿช","group":4,"order":3747,"tags":["chip","chocolate","dessert","sweet"],"version":0.6,"annotation":"cookie","shortcodes":["cookie"]},{"emoji":"๐ŸŽ‚","group":4,"order":3748,"tags":["bday","birthday","cake","celebration","dessert","happy","pastry","sweet"],"version":0.6,"annotation":"birthday cake","shortcodes":["birthday","birthday_cake"]},{"emoji":"๐Ÿฐ","group":4,"order":3749,"tags":["cake","dessert","pastry","slice","sweet"],"version":0.6,"annotation":"shortcake","shortcodes":["cake","shortcake"]},{"emoji":"๐Ÿง","group":4,"order":3750,"tags":["bakery","dessert","sprinkles","sugar","sweet","treat"],"version":11,"annotation":"cupcake","shortcodes":["cupcake"]},{"emoji":"๐Ÿฅง","group":4,"order":3751,"tags":["apple","filling","fruit","meat","pastry","pumpkin","slice"],"version":5,"annotation":"pie","shortcodes":["pie"]},{"emoji":"๐Ÿซ","group":4,"order":3752,"tags":["bar","candy","chocolate","dessert","halloween","sweet","tooth"],"version":0.6,"annotation":"chocolate bar","shortcodes":["chocolate_bar"]},{"emoji":"๐Ÿฌ","group":4,"order":3753,"tags":["cavities","dessert","halloween","restaurant","sweet","tooth","wrapper"],"version":0.6,"annotation":"candy","shortcodes":["candy"]},{"emoji":"๐Ÿญ","group":4,"order":3754,"tags":["candy","dessert","food","restaurant","sweet"],"version":0.6,"annotation":"lollipop","shortcodes":["lollipop"]},{"emoji":"๐Ÿฎ","group":4,"order":3755,"tags":["dessert","pudding","sweet"],"version":0.6,"annotation":"custard","shortcodes":["custard"]},{"emoji":"๐Ÿฏ","group":4,"order":3756,"tags":["barrel","bear","food","honey","honeypot","jar","pot","sweet"],"version":0.6,"annotation":"honey pot","shortcodes":["honey_pot"]},{"emoji":"๐Ÿผ","group":4,"order":3757,"tags":["babies","baby","birth","born","bottle","drink","infant","milk","newborn"],"version":1,"annotation":"baby bottle","shortcodes":["baby_bottle"]},{"emoji":"๐Ÿฅ›","group":4,"order":3758,"tags":["drink","glass","milk"],"version":3,"annotation":"glass of milk","shortcodes":["glass_of_milk","milk"]},{"emoji":"โ˜•๏ธ","group":4,"order":3759,"tags":["beverage","cafe","caffeine","chai","coffee","drink","hot","morning","steaming","tea"],"version":0.6,"annotation":"hot beverage","shortcodes":["coffee"]},{"emoji":"๐Ÿซ–","group":4,"order":3760,"tags":["brew","drink","food","pot","tea"],"version":13,"annotation":"teapot","shortcodes":["teapot"]},{"emoji":"๐Ÿต","group":4,"order":3761,"tags":["beverage","cup","drink","handle","oolong","tea","teacup"],"version":0.6,"annotation":"teacup without handle","shortcodes":["tea"]},{"emoji":"๐Ÿถ","group":4,"order":3762,"tags":["bar","beverage","bottle","cup","drink","restaurant"],"version":0.6,"annotation":"sake","shortcodes":["sake"]},{"emoji":"๐Ÿพ","group":4,"order":3763,"tags":["bar","bottle","cork","drink","popping"],"version":1,"annotation":"bottle with popping cork","shortcodes":["champagne"]},{"emoji":"๐Ÿท","group":4,"order":3764,"tags":["alcohol","bar","beverage","booze","club","drink","drinking","drinks","glass","restaurant","wine"],"version":0.6,"annotation":"wine glass","shortcodes":["wine_glass"]},{"emoji":"๐Ÿธ๏ธ","group":4,"order":3765,"tags":["alcohol","bar","booze","club","cocktail","drink","drinking","drinks","glass","mad","martini","men"],"version":0.6,"annotation":"cocktail glass","shortcodes":["cocktail"]},{"emoji":"๐Ÿน","group":4,"order":3766,"tags":["alcohol","bar","booze","club","cocktail","drink","drinking","drinks","drunk","mai","party","tai","tropical","tropics"],"version":0.6,"annotation":"tropical drink","shortcodes":["tropical_drink"]},{"emoji":"๐Ÿบ","group":4,"order":3767,"tags":["alcohol","ale","bar","beer","booze","drink","drinking","drinks","mug","octoberfest","oktoberfest","pint","stein","summer"],"version":0.6,"annotation":"beer mug","shortcodes":["beer"]},{"emoji":"๐Ÿป","group":4,"order":3768,"tags":["alcohol","bar","beer","booze","bottoms","cheers","clink","clinking","drinking","drinks","mugs"],"version":0.6,"annotation":"clinking beer mugs","shortcodes":["beers"]},{"emoji":"๐Ÿฅ‚","group":4,"order":3769,"tags":["celebrate","clink","clinking","drink","glass","glasses"],"version":3,"annotation":"clinking glasses","shortcodes":["clinking_glasses"]},{"emoji":"๐Ÿฅƒ","group":4,"order":3770,"tags":["glass","liquor","scotch","shot","tumbler","whiskey","whisky"],"version":3,"annotation":"tumbler glass","shortcodes":["tumbler_glass","whisky"]},{"emoji":"๐Ÿซ—","group":4,"order":3771,"tags":["accident","drink","empty","glass","liquid","oops","pour","pouring","spill","water"],"version":14,"annotation":"pouring liquid","shortcodes":["pour","pouring_liquid"]},{"emoji":"๐Ÿฅค","group":4,"order":3772,"tags":["cup","drink","juice","malt","soda","soft","straw","water"],"version":5,"annotation":"cup with straw","shortcodes":["cup_with_straw"]},{"emoji":"๐Ÿง‹","group":4,"order":3773,"tags":["boba","bubble","food","milk","pearl","tea"],"version":13,"annotation":"bubble tea","shortcodes":["boba_drink","bubble_tea"]},{"emoji":"๐Ÿงƒ","group":4,"order":3774,"tags":["beverage","box","juice","straw","sweet"],"version":12,"annotation":"beverage box","shortcodes":["beverage_box","juice_box"]},{"emoji":"๐Ÿง‰","group":4,"order":3775,"tags":["drink"],"version":12,"annotation":"mate","shortcodes":["mate"]},{"emoji":"๐ŸงŠ","group":4,"order":3776,"tags":["cold","cube","iceberg"],"version":12,"annotation":"ice","shortcodes":["ice","ice_cube"]},{"emoji":"๐Ÿฅข","group":4,"order":3777,"tags":["hashi","jeotgarak","kuaizi"],"version":5,"annotation":"chopsticks","shortcodes":["chopsticks"]},{"emoji":"๐Ÿฝ๏ธ","group":4,"order":3779,"tags":["cooking","dinner","eat","fork","knife","plate"],"version":0.7,"annotation":"fork and knife with plate","shortcodes":["fork_knife_plate"]},{"emoji":"๐Ÿด","group":4,"order":3780,"tags":["breakfast","breaky","cooking","cutlery","delicious","dinner","eat","feed","food","fork","hungry","knife","lunch","restaurant","yum","yummy"],"version":0.6,"annotation":"fork and knife","shortcodes":["fork_and_knife"]},{"emoji":"๐Ÿฅ„","group":4,"order":3781,"tags":["eat","tableware"],"version":3,"annotation":"spoon","shortcodes":["spoon"]},{"emoji":"๐Ÿ”ช","group":4,"order":3782,"tags":["chef","cooking","hocho","kitchen","knife","tool","weapon"],"version":0.6,"annotation":"kitchen knife","shortcodes":["knife"]},{"emoji":"๐Ÿซ™","group":4,"order":3783,"tags":["condiment","container","empty","nothing","sauce","store"],"version":14,"annotation":"jar","shortcodes":["jar"]},{"emoji":"๐Ÿบ","group":4,"order":3784,"tags":["aquarius","cooking","drink","jug","tool","weapon","zodiac"],"version":1,"annotation":"amphora","shortcodes":["amphora"]},{"emoji":"๐ŸŒ๏ธ","group":5,"order":3785,"tags":["africa","earth","europe","europe-africa","globe","showing","world"],"version":0.7,"annotation":"globe showing Europe-Africa","shortcodes":["earth_africa","earth_europe"]},{"emoji":"๐ŸŒŽ๏ธ","group":5,"order":3786,"tags":["americas","earth","globe","showing","world"],"version":0.7,"annotation":"globe showing Americas","shortcodes":["earth_americas"]},{"emoji":"๐ŸŒ๏ธ","group":5,"order":3787,"tags":["asia","asia-australia","australia","earth","globe","showing","world"],"version":0.6,"annotation":"globe showing Asia-Australia","shortcodes":["earth_asia"]},{"emoji":"๐ŸŒ","group":5,"order":3788,"tags":["earth","globe","internet","meridians","web","world","worldwide"],"version":1,"annotation":"globe with meridians","shortcodes":["globe_with_meridians"]},{"emoji":"๐Ÿ—บ๏ธ","group":5,"order":3790,"tags":["map","world"],"version":0.7,"annotation":"world map","shortcodes":["world_map"]},{"emoji":"๐Ÿ—พ","group":5,"order":3791,"tags":["japan","map"],"version":0.6,"annotation":"map of Japan","shortcodes":["japan_map"]},{"emoji":"๐Ÿงญ","group":5,"order":3792,"tags":["direction","magnetic","navigation","orienteering"],"version":11,"annotation":"compass","shortcodes":["compass"]},{"emoji":"๐Ÿ”๏ธ","group":5,"order":3794,"tags":["cold","mountain","snow","snow-capped"],"version":0.7,"annotation":"snow-capped mountain","shortcodes":["mountain_snow"]},{"emoji":"โ›ฐ๏ธ","group":5,"order":3796,"tags":["mountain"],"version":0.7,"annotation":"mountain","shortcodes":["mountain"]},{"emoji":"๐ŸŒ‹","group":5,"order":3797,"tags":["eruption","mountain","nature"],"version":0.6,"annotation":"volcano","shortcodes":["volcano"]},{"emoji":"๐Ÿ—ป","group":5,"order":3798,"tags":["fuji","mount","mountain","nature"],"version":0.6,"annotation":"mount fuji","shortcodes":["mount_fuji"]},{"emoji":"๐Ÿ•๏ธ","group":5,"order":3800,"tags":["camping"],"version":0.7,"annotation":"camping","shortcodes":["camping"]},{"emoji":"๐Ÿ–๏ธ","group":5,"order":3802,"tags":["beach","umbrella"],"version":0.7,"annotation":"beach with umbrella","shortcodes":["beach","beach_with_umbrella"]},{"emoji":"๐Ÿœ๏ธ","group":5,"order":3804,"tags":["desert"],"version":0.7,"annotation":"desert","shortcodes":["desert"]},{"emoji":"๐Ÿ๏ธ","group":5,"order":3806,"tags":["desert","island"],"version":0.7,"annotation":"desert island","shortcodes":["desert_island","island"]},{"emoji":"๐Ÿž๏ธ","group":5,"order":3808,"tags":["national","park"],"version":0.7,"annotation":"national park","shortcodes":["national_park"]},{"emoji":"๐ŸŸ๏ธ","group":5,"order":3810,"tags":["stadium"],"version":0.7,"annotation":"stadium","shortcodes":["stadium"]},{"emoji":"๐Ÿ›๏ธ","group":5,"order":3812,"tags":["building","classical"],"version":0.7,"annotation":"classical building","shortcodes":["classical_building"]},{"emoji":"๐Ÿ—๏ธ","group":5,"order":3814,"tags":["building","construction","crane"],"version":0.7,"annotation":"building construction","shortcodes":["building_construction","construction_site"]},{"emoji":"๐Ÿงฑ","group":5,"order":3815,"tags":["bricks","clay","mortar","wall"],"version":11,"annotation":"brick","shortcodes":["bricks"]},{"emoji":"๐Ÿชจ","group":5,"order":3816,"tags":["boulder","heavy","solid","stone","tough"],"version":13,"annotation":"rock","shortcodes":["rock"]},{"emoji":"๐Ÿชต","group":5,"order":3817,"tags":["log","lumber","timber"],"version":13,"annotation":"wood","shortcodes":["wood"]},{"emoji":"๐Ÿ›–","group":5,"order":3818,"tags":["home","house","roundhouse","shelter","yurt"],"version":13,"annotation":"hut","shortcodes":["hut"]},{"emoji":"๐Ÿ˜๏ธ","group":5,"order":3820,"tags":["house"],"version":0.7,"annotation":"houses","shortcodes":["homes","houses"]},{"emoji":"๐Ÿš๏ธ","group":5,"order":3822,"tags":["derelict","home","house"],"version":0.7,"annotation":"derelict house","shortcodes":["derelict_house","house_abandoned"]},{"emoji":"๐Ÿ ๏ธ","group":5,"order":3823,"tags":["building","country","heart","home","ranch","settle","simple","suburban","suburbia","where"],"version":0.6,"annotation":"house","shortcodes":["house"]},{"emoji":"๐Ÿก","group":5,"order":3824,"tags":["building","country","garden","heart","home","house","ranch","settle","simple","suburban","suburbia","where"],"version":0.6,"annotation":"house with garden","shortcodes":["house_with_garden"]},{"emoji":"๐Ÿข","group":5,"order":3825,"tags":["building","city","cubical","job","office"],"version":0.6,"annotation":"office building","shortcodes":["office"]},{"emoji":"๐Ÿฃ","group":5,"order":3826,"tags":["building","japanese","office","post"],"version":0.6,"annotation":"Japanese post office","shortcodes":["post_office"]},{"emoji":"๐Ÿค","group":5,"order":3827,"tags":["building","european","office","post"],"version":1,"annotation":"post office","shortcodes":["european_post_office"]},{"emoji":"๐Ÿฅ","group":5,"order":3828,"tags":["building","doctor","medicine"],"version":0.6,"annotation":"hospital","shortcodes":["hospital"]},{"emoji":"๐Ÿฆ","group":5,"order":3829,"tags":["building"],"version":0.6,"annotation":"bank","shortcodes":["bank"]},{"emoji":"๐Ÿจ","group":5,"order":3830,"tags":["building"],"version":0.6,"annotation":"hotel","shortcodes":["hotel"]},{"emoji":"๐Ÿฉ","group":5,"order":3831,"tags":["building","hotel","love"],"version":0.6,"annotation":"love hotel","shortcodes":["love_hotel"]},{"emoji":"๐Ÿช","group":5,"order":3832,"tags":["24","building","convenience","hours","store"],"version":0.6,"annotation":"convenience store","shortcodes":["convenience_store"]},{"emoji":"๐Ÿซ","group":5,"order":3833,"tags":["building"],"version":0.6,"annotation":"school","shortcodes":["school"]},{"emoji":"๐Ÿฌ","group":5,"order":3834,"tags":["building","department","store"],"version":0.6,"annotation":"department store","shortcodes":["department_store"]},{"emoji":"๐Ÿญ๏ธ","group":5,"order":3835,"tags":["building"],"version":0.6,"annotation":"factory","shortcodes":["factory"]},{"emoji":"๐Ÿฏ","group":5,"order":3836,"tags":["building","castle","japanese"],"version":0.6,"annotation":"Japanese castle","shortcodes":["japanese_castle"]},{"emoji":"๐Ÿฐ","group":5,"order":3837,"tags":["building","european"],"version":0.6,"annotation":"castle","shortcodes":["castle","european_castle"]},{"emoji":"๐Ÿ’’","group":5,"order":3838,"tags":["chapel","hitched","nuptials","romance"],"version":0.6,"annotation":"wedding","shortcodes":["wedding"]},{"emoji":"๐Ÿ—ผ","group":5,"order":3839,"tags":["tokyo","tower"],"version":0.6,"annotation":"Tokyo tower","shortcodes":["tokyo_tower"]},{"emoji":"๐Ÿ—ฝ","group":5,"order":3840,"tags":["liberty","new","ny","nyc","statue","york"],"version":0.6,"annotation":"Statue of Liberty","shortcodes":["statue_of_liberty"]},{"emoji":"โ›ช๏ธ","group":5,"order":3841,"tags":["bless","chapel","christian","cross","religion"],"version":0.6,"annotation":"church","shortcodes":["church"]},{"emoji":"๐Ÿ•Œ","group":5,"order":3842,"tags":["islam","masjid","muslim","religion"],"version":1,"annotation":"mosque","shortcodes":["mosque"]},{"emoji":"๐Ÿ›•","group":5,"order":3843,"tags":["hindu","temple"],"version":12,"annotation":"hindu temple","shortcodes":["hindu_temple"]},{"emoji":"๐Ÿ•","group":5,"order":3844,"tags":["jew","jewish","judaism","religion","temple"],"version":1,"annotation":"synagogue","shortcodes":["synagogue"]},{"emoji":"โ›ฉ๏ธ","group":5,"order":3846,"tags":["religion","shinto","shrine"],"version":0.7,"annotation":"shinto shrine","shortcodes":["shinto_shrine"]},{"emoji":"๐Ÿ•‹","group":5,"order":3847,"tags":["hajj","islam","muslim","religion","umrah"],"version":1,"annotation":"kaaba","shortcodes":["kaaba"]},{"emoji":"โ›ฒ๏ธ","group":5,"order":3848,"tags":["fountain"],"version":0.6,"annotation":"fountain","shortcodes":["fountain"]},{"emoji":"โ›บ๏ธ","group":5,"order":3849,"tags":["camping"],"version":0.6,"annotation":"tent","shortcodes":["tent"]},{"emoji":"๐ŸŒ","group":5,"order":3850,"tags":["fog"],"version":0.6,"annotation":"foggy","shortcodes":["foggy"]},{"emoji":"๐ŸŒƒ","group":5,"order":3851,"tags":["night","star","stars"],"version":0.6,"annotation":"night with stars","shortcodes":["night_with_stars"]},{"emoji":"๐Ÿ™๏ธ","group":5,"order":3853,"tags":["city"],"version":0.7,"annotation":"cityscape","shortcodes":["cityscape"]},{"emoji":"๐ŸŒ„","group":5,"order":3854,"tags":["morning","mountains","over","sun","sunrise"],"version":0.6,"annotation":"sunrise over mountains","shortcodes":["sunrise_over_mountains"]},{"emoji":"๐ŸŒ…","group":5,"order":3855,"tags":["morning","nature","sun"],"version":0.6,"annotation":"sunrise","shortcodes":["sunrise"]},{"emoji":"๐ŸŒ†","group":5,"order":3856,"tags":["at","building","city","cityscape","dusk","evening","landscape","sun","sunset"],"version":0.6,"annotation":"cityscape at dusk","shortcodes":["city_dusk"]},{"emoji":"๐ŸŒ‡","group":5,"order":3857,"tags":["building","dusk","sun"],"version":0.6,"annotation":"sunset","shortcodes":["city_sunrise","city_sunset"]},{"emoji":"๐ŸŒ‰","group":5,"order":3858,"tags":["at","bridge","night"],"version":0.6,"annotation":"bridge at night","shortcodes":["bridge_at_night"]},{"emoji":"โ™จ๏ธ","group":5,"order":3860,"tags":["hot","hotsprings","springs","steaming"],"version":0.6,"annotation":"hot springs","shortcodes":["hotsprings"]},{"emoji":"๐ŸŽ ","group":5,"order":3861,"tags":["carousel","entertainment","horse"],"version":0.6,"annotation":"carousel horse","shortcodes":["carousel_horse"]},{"emoji":"๐Ÿ›","group":5,"order":3862,"tags":["amusement","park","play","playground","playing","slide","sliding","theme"],"version":14,"annotation":"playground slide","shortcodes":["playground_slide","slide"]},{"emoji":"๐ŸŽก","group":5,"order":3863,"tags":["amusement","ferris","park","theme","wheel"],"version":0.6,"annotation":"ferris wheel","shortcodes":["ferris_wheel"]},{"emoji":"๐ŸŽข","group":5,"order":3864,"tags":["amusement","coaster","park","roller","theme"],"version":0.6,"annotation":"roller coaster","shortcodes":["roller_coaster"]},{"emoji":"๐Ÿ’ˆ","group":5,"order":3865,"tags":["barber","cut","fresh","haircut","pole","shave"],"version":0.6,"annotation":"barber pole","shortcodes":["barber","barber_pole"]},{"emoji":"๐ŸŽช","group":5,"order":3866,"tags":["circus","tent"],"version":0.6,"annotation":"circus tent","shortcodes":["circus_tent"]},{"emoji":"๐Ÿš‚","group":5,"order":3867,"tags":["caboose","engine","railway","steam","train","trains","travel"],"version":1,"annotation":"locomotive","shortcodes":["steam_locomotive"]},{"emoji":"๐Ÿšƒ","group":5,"order":3868,"tags":["car","electric","railway","train","tram","travel","trolleybus"],"version":0.6,"annotation":"railway car","shortcodes":["railway_car"]},{"emoji":"๐Ÿš„","group":5,"order":3869,"tags":["high-speed","railway","shinkansen","speed","train"],"version":0.6,"annotation":"high-speed train","shortcodes":["bullettrain_side"]},{"emoji":"๐Ÿš…","group":5,"order":3870,"tags":["bullet","high-speed","nose","railway","shinkansen","speed","train","travel"],"version":0.6,"annotation":"bullet train","shortcodes":["bullettrain_front"]},{"emoji":"๐Ÿš†","group":5,"order":3871,"tags":["arrived","choo","railway"],"version":1,"annotation":"train","shortcodes":["train"]},{"emoji":"๐Ÿš‡๏ธ","group":5,"order":3872,"tags":["subway","travel"],"version":0.6,"annotation":"metro","shortcodes":["metro"]},{"emoji":"๐Ÿšˆ","group":5,"order":3873,"tags":["arrived","light","monorail","rail","railway"],"version":1,"annotation":"light rail","shortcodes":["light_rail"]},{"emoji":"๐Ÿš‰","group":5,"order":3874,"tags":["railway","train"],"version":0.6,"annotation":"station","shortcodes":["station"]},{"emoji":"๐ŸšŠ","group":5,"order":3875,"tags":["trolleybus"],"version":1,"annotation":"tram","shortcodes":["tram"]},{"emoji":"๐Ÿš","group":5,"order":3876,"tags":["vehicle"],"version":1,"annotation":"monorail","shortcodes":["monorail"]},{"emoji":"๐Ÿšž","group":5,"order":3877,"tags":["car","mountain","railway","trip"],"version":1,"annotation":"mountain railway","shortcodes":["mountain_railway"]},{"emoji":"๐Ÿš‹","group":5,"order":3878,"tags":["bus","car","tram","trolley","trolleybus"],"version":1,"annotation":"tram car","shortcodes":["tram_car"]},{"emoji":"๐ŸšŒ","group":5,"order":3879,"tags":["school","vehicle"],"version":0.6,"annotation":"bus","shortcodes":["bus"]},{"emoji":"๐Ÿš๏ธ","group":5,"order":3880,"tags":["bus","cars","oncoming"],"version":0.7,"annotation":"oncoming bus","shortcodes":["oncoming_bus"]},{"emoji":"๐ŸšŽ","group":5,"order":3881,"tags":["bus","tram","trolley"],"version":1,"annotation":"trolleybus","shortcodes":["trolleybus"]},{"emoji":"๐Ÿš","group":5,"order":3882,"tags":["bus","drive","van","vehicle"],"version":1,"annotation":"minibus","shortcodes":["minibus"]},{"emoji":"๐Ÿš‘๏ธ","group":5,"order":3883,"tags":["emergency","vehicle"],"version":0.6,"annotation":"ambulance","shortcodes":["ambulance"]},{"emoji":"๐Ÿš’","group":5,"order":3884,"tags":["engine","fire","truck"],"version":0.6,"annotation":"fire engine","shortcodes":["fire_engine"]},{"emoji":"๐Ÿš“","group":5,"order":3885,"tags":["5โ€“0","car","cops","patrol","police"],"version":0.6,"annotation":"police car","shortcodes":["police_car"]},{"emoji":"๐Ÿš”๏ธ","group":5,"order":3886,"tags":["car","oncoming","police"],"version":0.7,"annotation":"oncoming police car","shortcodes":["oncoming_police_car"]},{"emoji":"๐Ÿš•","group":5,"order":3887,"tags":["cab","cabbie","car","drive","vehicle","yellow"],"version":0.6,"annotation":"taxi","shortcodes":["taxi"]},{"emoji":"๐Ÿš–","group":5,"order":3888,"tags":["cab","cabbie","cars","drove","hail","oncoming","taxi","yellow"],"version":1,"annotation":"oncoming taxi","shortcodes":["oncoming_taxi"]},{"emoji":"๐Ÿš—","group":5,"order":3889,"tags":["car","driving","vehicle"],"version":0.6,"annotation":"automobile","shortcodes":["car","red_car"]},{"emoji":"๐Ÿš˜๏ธ","group":5,"order":3890,"tags":["automobile","car","cars","drove","oncoming","vehicle"],"version":0.7,"annotation":"oncoming automobile","shortcodes":["oncoming_automobile"]},{"emoji":"๐Ÿš™","group":5,"order":3891,"tags":["car","drive","recreational","sport","sportutility","utility","vehicle"],"version":0.6,"annotation":"sport utility vehicle","shortcodes":["blue_car","suv"]},{"emoji":"๐Ÿ›ป","group":5,"order":3892,"tags":["automobile","car","flatbed","pick-up","pickup","transportation","truck"],"version":13,"annotation":"pickup truck","shortcodes":["pickup_truck"]},{"emoji":"๐Ÿšš","group":5,"order":3893,"tags":["car","delivery","drive","truck","vehicle"],"version":0.6,"annotation":"delivery truck","shortcodes":["delivery_truck","truck"]},{"emoji":"๐Ÿš›","group":5,"order":3894,"tags":["articulated","car","drive","lorry","move","semi","truck","vehicle"],"version":1,"annotation":"articulated lorry","shortcodes":["articulated_lorry"]},{"emoji":"๐Ÿšœ","group":5,"order":3895,"tags":["vehicle"],"version":1,"annotation":"tractor","shortcodes":["tractor"]},{"emoji":"๐ŸŽ๏ธ","group":5,"order":3897,"tags":["car","racing","zoom"],"version":0.7,"annotation":"racing car","shortcodes":["racing_car"]},{"emoji":"๐Ÿ๏ธ","group":5,"order":3899,"tags":["racing"],"version":0.7,"annotation":"motorcycle","shortcodes":["motorcycle"]},{"emoji":"๐Ÿ›ต","group":5,"order":3900,"tags":["motor","scooter"],"version":3,"annotation":"motor scooter","shortcodes":["motor_scooter"]},{"emoji":"๐Ÿฆฝ","group":5,"order":3901,"tags":["accessibility","manual","wheelchair"],"version":12,"annotation":"manual wheelchair","shortcodes":["manual_wheelchair"]},{"emoji":"๐Ÿฆผ","group":5,"order":3902,"tags":["accessibility","motorized","wheelchair"],"version":12,"annotation":"motorized wheelchair","shortcodes":["motorized_wheelchair"]},{"emoji":"๐Ÿ›บ","group":5,"order":3903,"tags":["auto","rickshaw","tuk"],"version":12,"annotation":"auto rickshaw","shortcodes":["auto_rickshaw"]},{"emoji":"๐Ÿšฒ๏ธ","group":5,"order":3904,"tags":["bike","class","cycle","cycling","cyclist","gang","ride","spin","spinning"],"version":0.6,"annotation":"bicycle","shortcodes":["bicycle","bike"]},{"emoji":"๐Ÿ›ด","group":5,"order":3905,"tags":["kick","scooter"],"version":3,"annotation":"kick scooter","shortcodes":["scooter"]},{"emoji":"๐Ÿ›น","group":5,"order":3906,"tags":["board","skate","skater","wheels"],"version":11,"annotation":"skateboard","shortcodes":["skateboard"]},{"emoji":"๐Ÿ›ผ","group":5,"order":3907,"tags":["blades","roller","skate","skates","sport"],"version":13,"annotation":"roller skate","shortcodes":["roller_skate"]},{"emoji":"๐Ÿš","group":5,"order":3908,"tags":["bus","busstop","stop"],"version":0.6,"annotation":"bus stop","shortcodes":["busstop"]},{"emoji":"๐Ÿ›ฃ๏ธ","group":5,"order":3910,"tags":["highway","road"],"version":0.7,"annotation":"motorway","shortcodes":["motorway"]},{"emoji":"๐Ÿ›ค๏ธ","group":5,"order":3912,"tags":["railway","track","train"],"version":0.7,"annotation":"railway track","shortcodes":["railway_track"]},{"emoji":"๐Ÿ›ข๏ธ","group":5,"order":3914,"tags":["drum","oil"],"version":0.7,"annotation":"oil drum","shortcodes":["oil_drum"]},{"emoji":"โ›ฝ๏ธ","group":5,"order":3915,"tags":["diesel","fuel","fuelpump","gas","gasoline","pump","station"],"version":0.6,"annotation":"fuel pump","shortcodes":["fuelpump"]},{"emoji":"๐Ÿ›ž","group":5,"order":3916,"tags":["car","circle","tire","turn","vehicle"],"version":14,"annotation":"wheel","shortcodes":["wheel"]},{"emoji":"๐Ÿšจ","group":5,"order":3917,"tags":["alarm","alert","beacon","car","emergency","light","police","revolving","siren"],"version":0.6,"annotation":"police car light","shortcodes":["rotating_light"]},{"emoji":"๐Ÿšฅ","group":5,"order":3918,"tags":["horizontal","intersection","light","signal","stop","stoplight","traffic"],"version":0.6,"annotation":"horizontal traffic light","shortcodes":["traffic_light"]},{"emoji":"๐Ÿšฆ","group":5,"order":3919,"tags":["drove","intersection","light","signal","stop","stoplight","traffic","vertical"],"version":1,"annotation":"vertical traffic light","shortcodes":["vertical_traffic_light"]},{"emoji":"๐Ÿ›‘","group":5,"order":3920,"tags":["octagonal","sign","stop"],"version":3,"annotation":"stop sign","shortcodes":["octagonal_sign","stop_sign"]},{"emoji":"๐Ÿšง","group":5,"order":3921,"tags":["barrier"],"version":0.6,"annotation":"construction","shortcodes":["construction"]},{"emoji":"โš“๏ธ","group":5,"order":3922,"tags":["ship","tool"],"version":0.6,"annotation":"anchor","shortcodes":["anchor"]},{"emoji":"๐Ÿ›Ÿ","group":5,"order":3923,"tags":["buoy","float","life","lifesaver","preserver","rescue","ring","safety","save","saver","swim"],"version":14,"annotation":"ring buoy","shortcodes":["lifebuoy","ring_buoy"]},{"emoji":"โ›ต๏ธ","group":5,"order":3924,"tags":["boat","resort","sailing","sea","yacht"],"version":0.6,"annotation":"sailboat","shortcodes":["sailboat"]},{"emoji":"๐Ÿ›ถ","group":5,"order":3925,"tags":["boat"],"version":3,"annotation":"canoe","shortcodes":["canoe"]},{"emoji":"๐Ÿšค","group":5,"order":3926,"tags":["billionaire","boat","lake","luxury","millionaire","summer","travel"],"version":0.6,"annotation":"speedboat","shortcodes":["speedboat"]},{"emoji":"๐Ÿ›ณ๏ธ","group":5,"order":3928,"tags":["passenger","ship"],"version":0.7,"annotation":"passenger ship","shortcodes":["cruise_ship","passenger_ship"]},{"emoji":"โ›ด๏ธ","group":5,"order":3930,"tags":["boat","passenger"],"version":0.7,"annotation":"ferry","shortcodes":["ferry"]},{"emoji":"๐Ÿ›ฅ๏ธ","group":5,"order":3932,"tags":["boat","motor","motorboat"],"version":0.7,"annotation":"motor boat","shortcodes":["motorboat"]},{"emoji":"๐Ÿšข","group":5,"order":3933,"tags":["boat","passenger","travel"],"version":0.6,"annotation":"ship","shortcodes":["ship"]},{"emoji":"โœˆ๏ธ","group":5,"order":3935,"tags":["aeroplane","fly","flying","jet","plane","travel"],"version":0.6,"annotation":"airplane","shortcodes":["airplane"]},{"emoji":"๐Ÿ›ฉ๏ธ","group":5,"order":3937,"tags":["aeroplane","airplane","plane","small"],"version":0.7,"annotation":"small airplane","shortcodes":["small_airplane"]},{"emoji":"๐Ÿ›ซ","group":5,"order":3938,"tags":["aeroplane","airplane","check-in","departure","departures","plane"],"version":1,"annotation":"airplane departure","shortcodes":["airplane_departure"]},{"emoji":"๐Ÿ›ฌ","group":5,"order":3939,"tags":["aeroplane","airplane","arrival","arrivals","arriving","landing","plane"],"version":1,"annotation":"airplane arrival","shortcodes":["airplane_arriving"]},{"emoji":"๐Ÿช‚","group":5,"order":3940,"tags":["hang-glide","parasail","skydive"],"version":12,"annotation":"parachute","shortcodes":["parachute"]},{"emoji":"๐Ÿ’บ","group":5,"order":3941,"tags":["chair"],"version":0.6,"annotation":"seat","shortcodes":["seat"]},{"emoji":"๐Ÿš","group":5,"order":3942,"tags":["copter","roflcopter","travel","vehicle"],"version":1,"annotation":"helicopter","shortcodes":["helicopter"]},{"emoji":"๐ŸšŸ","group":5,"order":3943,"tags":["railway","suspension"],"version":1,"annotation":"suspension railway","shortcodes":["suspension_railway"]},{"emoji":"๐Ÿš ","group":5,"order":3944,"tags":["cable","cableway","gondola","lift","mountain","ski"],"version":1,"annotation":"mountain cableway","shortcodes":["mountain_cableway"]},{"emoji":"๐Ÿšก","group":5,"order":3945,"tags":["aerial","cable","car","gondola","ropeway","tramway"],"version":1,"annotation":"aerial tramway","shortcodes":["aerial_tramway"]},{"emoji":"๐Ÿ›ฐ๏ธ","group":5,"order":3947,"tags":["space"],"version":0.7,"annotation":"satellite","shortcodes":["satellite"]},{"emoji":"๐Ÿš€","group":5,"order":3948,"tags":["launch","rockets","space","travel"],"version":0.6,"annotation":"rocket","shortcodes":["rocket"]},{"emoji":"๐Ÿ›ธ","group":5,"order":3949,"tags":["aliens","extra","flying","saucer","terrestrial","ufo"],"version":5,"annotation":"flying saucer","shortcodes":["flying_saucer"]},{"emoji":"๐Ÿ›Ž๏ธ","group":5,"order":3951,"tags":["bell","bellhop","hotel"],"version":0.7,"annotation":"bellhop bell","shortcodes":["bellhop"]},{"emoji":"๐Ÿงณ","group":5,"order":3952,"tags":["bag","packing","roller","suitcase","travel"],"version":11,"annotation":"luggage","shortcodes":["luggage"]},{"emoji":"โŒ›๏ธ","group":5,"order":3953,"tags":["done","hourglass","sand","time","timer"],"version":0.6,"annotation":"hourglass done","shortcodes":["hourglass"]},{"emoji":"โณ๏ธ","group":5,"order":3954,"tags":["done","flowing","hourglass","hours","not","sand","timer","waiting","yolo"],"version":0.6,"annotation":"hourglass not done","shortcodes":["hourglass_flowing_sand"]},{"emoji":"โŒš๏ธ","group":5,"order":3955,"tags":["clock","time"],"version":0.6,"annotation":"watch","shortcodes":["watch"]},{"emoji":"โฐ๏ธ","group":5,"order":3956,"tags":["alarm","clock","hours","hrs","late","time","waiting"],"version":0.6,"annotation":"alarm clock","shortcodes":["alarm_clock"]},{"emoji":"โฑ๏ธ","group":5,"order":3958,"tags":["clock","time"],"version":1,"annotation":"stopwatch","shortcodes":["stopwatch"]},{"emoji":"โฒ๏ธ","group":5,"order":3960,"tags":["clock","timer"],"version":1,"annotation":"timer clock","shortcodes":["timer_clock"]},{"emoji":"๐Ÿ•ฐ๏ธ","group":5,"order":3962,"tags":["clock","mantelpiece","time"],"version":0.7,"annotation":"mantelpiece clock","shortcodes":["clock"]},{"emoji":"๐Ÿ•›๏ธ","group":5,"order":3963,"tags":["12","12:00","clock","oโ€™clock","time","twelve"],"version":0.6,"annotation":"twelve oโ€™clock","shortcodes":["clock12"]},{"emoji":"๐Ÿ•ง๏ธ","group":5,"order":3964,"tags":["12","12:30","30","clock","thirty","time","twelve"],"version":0.7,"annotation":"twelve-thirty","shortcodes":["clock1230"]},{"emoji":"๐Ÿ•๏ธ","group":5,"order":3965,"tags":["1","1:00","clock","one","oโ€™clock","time"],"version":0.6,"annotation":"one oโ€™clock","shortcodes":["clock1"]},{"emoji":"๐Ÿ•œ๏ธ","group":5,"order":3966,"tags":["1","1:30","30","clock","one","thirty","time"],"version":0.7,"annotation":"one-thirty","shortcodes":["clock130"]},{"emoji":"๐Ÿ•‘๏ธ","group":5,"order":3967,"tags":["2","2:00","clock","oโ€™clock","time","two"],"version":0.6,"annotation":"two oโ€™clock","shortcodes":["clock2"]},{"emoji":"๐Ÿ•๏ธ","group":5,"order":3968,"tags":["2","2:30","30","clock","thirty","time","two"],"version":0.7,"annotation":"two-thirty","shortcodes":["clock230"]},{"emoji":"๐Ÿ•’๏ธ","group":5,"order":3969,"tags":["3","3:00","clock","oโ€™clock","three","time"],"version":0.6,"annotation":"three oโ€™clock","shortcodes":["clock3"]},{"emoji":"๐Ÿ•ž๏ธ","group":5,"order":3970,"tags":["3","30","3:30","clock","thirty","three","time"],"version":0.7,"annotation":"three-thirty","shortcodes":["clock330"]},{"emoji":"๐Ÿ•“๏ธ","group":5,"order":3971,"tags":["4","4:00","clock","four","oโ€™clock","time"],"version":0.6,"annotation":"four oโ€™clock","shortcodes":["clock4"]},{"emoji":"๐Ÿ•Ÿ๏ธ","group":5,"order":3972,"tags":["30","4","4:30","clock","four","thirty","time"],"version":0.7,"annotation":"four-thirty","shortcodes":["clock430"]},{"emoji":"๐Ÿ•”๏ธ","group":5,"order":3973,"tags":["5","5:00","clock","five","oโ€™clock","time"],"version":0.6,"annotation":"five oโ€™clock","shortcodes":["clock5"]},{"emoji":"๐Ÿ• ๏ธ","group":5,"order":3974,"tags":["30","5","5:30","clock","five","thirty","time"],"version":0.7,"annotation":"five-thirty","shortcodes":["clock530"]},{"emoji":"๐Ÿ••๏ธ","group":5,"order":3975,"tags":["6","6:00","clock","oโ€™clock","six","time"],"version":0.6,"annotation":"six oโ€™clock","shortcodes":["clock6"]},{"emoji":"๐Ÿ•ก๏ธ","group":5,"order":3976,"tags":["30","6","6:30","clock","six","thirty"],"version":0.7,"annotation":"six-thirty","shortcodes":["clock630"]},{"emoji":"๐Ÿ•–๏ธ","group":5,"order":3977,"tags":["0","7","7:00","clock","oโ€™clock","seven"],"version":0.6,"annotation":"seven oโ€™clock","shortcodes":["clock7"]},{"emoji":"๐Ÿ•ข๏ธ","group":5,"order":3978,"tags":["30","7","7:30","clock","seven","thirty"],"version":0.7,"annotation":"seven-thirty","shortcodes":["clock730"]},{"emoji":"๐Ÿ•—๏ธ","group":5,"order":3979,"tags":["8","8:00","clock","eight","oโ€™clock","time"],"version":0.6,"annotation":"eight oโ€™clock","shortcodes":["clock8"]},{"emoji":"๐Ÿ•ฃ๏ธ","group":5,"order":3980,"tags":["30","8","8:30","clock","eight","thirty","time"],"version":0.7,"annotation":"eight-thirty","shortcodes":["clock830"]},{"emoji":"๐Ÿ•˜๏ธ","group":5,"order":3981,"tags":["9","9:00","clock","nine","oโ€™clock","time"],"version":0.6,"annotation":"nine oโ€™clock","shortcodes":["clock9"]},{"emoji":"๐Ÿ•ค๏ธ","group":5,"order":3982,"tags":["30","9","9:30","clock","nine","thirty","time"],"version":0.7,"annotation":"nine-thirty","shortcodes":["clock930"]},{"emoji":"๐Ÿ•™๏ธ","group":5,"order":3983,"tags":["0","10","10:00","clock","oโ€™clock","ten"],"version":0.6,"annotation":"ten oโ€™clock","shortcodes":["clock10"]},{"emoji":"๐Ÿ•ฅ๏ธ","group":5,"order":3984,"tags":["10","10:30","30","clock","ten","thirty","time"],"version":0.7,"annotation":"ten-thirty","shortcodes":["clock1030"]},{"emoji":"๐Ÿ•š๏ธ","group":5,"order":3985,"tags":["11","11:00","clock","eleven","oโ€™clock","time"],"version":0.6,"annotation":"eleven oโ€™clock","shortcodes":["clock11"]},{"emoji":"๐Ÿ•ฆ๏ธ","group":5,"order":3986,"tags":["11","11:30","30","clock","eleven","thirty","time"],"version":0.7,"annotation":"eleven-thirty","shortcodes":["clock1130"]},{"emoji":"๐ŸŒ‘","group":5,"order":3987,"tags":["dark","moon","new","space"],"version":0.6,"annotation":"new moon","shortcodes":["new_moon"]},{"emoji":"๐ŸŒ’","group":5,"order":3988,"tags":["crescent","dreams","moon","space","waxing"],"version":1,"annotation":"waxing crescent moon","shortcodes":["waxing_crescent_moon"]},{"emoji":"๐ŸŒ“","group":5,"order":3989,"tags":["first","moon","quarter","space"],"version":0.6,"annotation":"first quarter moon","shortcodes":["first_quarter_moon"]},{"emoji":"๐ŸŒ”","group":5,"order":3990,"tags":["gibbous","moon","space","waxing"],"version":0.6,"annotation":"waxing gibbous moon","shortcodes":["waxing_gibbous_moon"]},{"emoji":"๐ŸŒ•๏ธ","group":5,"order":3991,"tags":["full","moon","space"],"version":0.6,"annotation":"full moon","shortcodes":["full_moon"]},{"emoji":"๐ŸŒ–","group":5,"order":3992,"tags":["gibbous","moon","space","waning"],"version":1,"annotation":"waning gibbous moon","shortcodes":["waning_gibbous_moon"]},{"emoji":"๐ŸŒ—","group":5,"order":3993,"tags":["last","moon","quarter","space"],"version":1,"annotation":"last quarter moon","shortcodes":["last_quarter_moon"]},{"emoji":"๐ŸŒ˜","group":5,"order":3994,"tags":["crescent","moon","space","waning"],"version":1,"annotation":"waning crescent moon","shortcodes":["waning_crescent_moon"]},{"emoji":"๐ŸŒ™","group":5,"order":3995,"tags":["crescent","moon","ramadan","space"],"version":0.6,"annotation":"crescent moon","shortcodes":["crescent_moon"]},{"emoji":"๐ŸŒš","group":5,"order":3996,"tags":["face","moon","new","space"],"version":1,"annotation":"new moon face","shortcodes":["new_moon_with_face"]},{"emoji":"๐ŸŒ›","group":5,"order":3997,"tags":["face","first","moon","quarter","space"],"version":0.6,"annotation":"first quarter moon face","shortcodes":["first_quarter_moon_with_face"]},{"emoji":"๐ŸŒœ๏ธ","group":5,"order":3998,"tags":["dreams","face","last","moon","quarter"],"version":0.7,"annotation":"last quarter moon face","shortcodes":["last_quarter_moon_with_face"]},{"emoji":"๐ŸŒก๏ธ","group":5,"order":4000,"tags":["weather"],"version":0.7,"annotation":"thermometer","shortcodes":["thermometer"]},{"emoji":"โ˜€๏ธ","group":5,"order":4002,"tags":["bright","rays","space","sunny","weather"],"version":0.6,"annotation":"sun","shortcodes":["sun"]},{"emoji":"๐ŸŒ","group":5,"order":4003,"tags":["bright","face","full","moon"],"version":1,"annotation":"full moon face","shortcodes":["full_moon_with_face"]},{"emoji":"๐ŸŒž","group":5,"order":4004,"tags":["beach","bright","day","face","heat","shine","sun","sunny","sunshine","weather"],"version":1,"annotation":"sun with face","shortcodes":["sun_with_face"]},{"emoji":"๐Ÿช","group":5,"order":4005,"tags":["planet","ringed","saturn","saturnine"],"version":12,"annotation":"ringed planet","shortcodes":["ringed_planet","saturn"]},{"emoji":"โญ๏ธ","group":5,"order":4006,"tags":["astronomy","medium","stars","white"],"version":0.6,"annotation":"star","shortcodes":["star"]},{"emoji":"๐ŸŒŸ","group":5,"order":4007,"tags":["glittery","glow","glowing","night","shining","sparkle","star","win"],"version":0.6,"annotation":"glowing star","shortcodes":["glowing_star","star2"]},{"emoji":"๐ŸŒ ","group":5,"order":4008,"tags":["falling","night","shooting","space","star"],"version":0.6,"annotation":"shooting star","shortcodes":["shooting_star","stars"]},{"emoji":"๐ŸŒŒ","group":5,"order":4009,"tags":["milky","space","way"],"version":0.6,"annotation":"milky way","shortcodes":["milky_way"]},{"emoji":"โ˜๏ธ","group":5,"order":4011,"tags":["weather"],"version":0.6,"annotation":"cloud","shortcodes":["cloud"]},{"emoji":"โ›…๏ธ","group":5,"order":4012,"tags":["behind","cloud","cloudy","sun","weather"],"version":0.6,"annotation":"sun behind cloud","shortcodes":["partly_sunny","sun_behind_cloud"]},{"emoji":"โ›ˆ๏ธ","group":5,"order":4014,"tags":["cloud","lightning","rain","thunder","thunderstorm"],"version":0.7,"annotation":"cloud with lightning and rain","shortcodes":["stormy","thunder_cloud_and_rain"]},{"emoji":"๐ŸŒค๏ธ","group":5,"order":4016,"tags":["behind","cloud","sun","weather"],"version":0.7,"annotation":"sun behind small cloud","shortcodes":["sun_behind_small_cloud","sunny"]},{"emoji":"๐ŸŒฅ๏ธ","group":5,"order":4018,"tags":["behind","cloud","sun","weather"],"version":0.7,"annotation":"sun behind large cloud","shortcodes":["cloudy","sun_behind_large_cloud"]},{"emoji":"๐ŸŒฆ๏ธ","group":5,"order":4020,"tags":["behind","cloud","rain","sun","weather"],"version":0.7,"annotation":"sun behind rain cloud","shortcodes":["sun_and_rain","sun_behind_rain_cloud"]},{"emoji":"๐ŸŒง๏ธ","group":5,"order":4022,"tags":["cloud","rain","weather"],"version":0.7,"annotation":"cloud with rain","shortcodes":["cloud_with_rain","rainy"]},{"emoji":"๐ŸŒจ๏ธ","group":5,"order":4024,"tags":["cloud","cold","snow","weather"],"version":0.7,"annotation":"cloud with snow","shortcodes":["cloud_with_snow","snowy"]},{"emoji":"๐ŸŒฉ๏ธ","group":5,"order":4026,"tags":["cloud","lightning","weather"],"version":0.7,"annotation":"cloud with lightning","shortcodes":["cloud_with_lightning","lightning"]},{"emoji":"๐ŸŒช๏ธ","group":5,"order":4028,"tags":["cloud","weather","whirlwind"],"version":0.7,"annotation":"tornado","shortcodes":["tornado"]},{"emoji":"๐ŸŒซ๏ธ","group":5,"order":4030,"tags":["cloud","weather"],"version":0.7,"annotation":"fog","shortcodes":["fog"]},{"emoji":"๐ŸŒฌ๏ธ","group":5,"order":4032,"tags":["blow","cloud","face","wind"],"version":0.7,"annotation":"wind face","shortcodes":["wind_blowing_face"]},{"emoji":"๐ŸŒ€","group":5,"order":4033,"tags":["dizzy","hurricane","twister","typhoon","weather"],"version":0.6,"annotation":"cyclone","shortcodes":["cyclone"]},{"emoji":"๐ŸŒˆ","group":5,"order":4034,"tags":["gay","genderqueer","glbt","glbtq","lesbian","lgbt","lgbtq","lgbtqia","nature","pride","queer","rain","trans","transgender","weather"],"version":0.6,"annotation":"rainbow","shortcodes":["rainbow"]},{"emoji":"๐ŸŒ‚","group":5,"order":4035,"tags":["closed","clothing","rain","umbrella"],"version":0.6,"annotation":"closed umbrella","shortcodes":["closed_umbrella"]},{"emoji":"โ˜‚๏ธ","group":5,"order":4037,"tags":["clothing","rain"],"version":0.7,"annotation":"umbrella","shortcodes":["umbrella"]},{"emoji":"โ˜”๏ธ","group":5,"order":4038,"tags":["clothing","drop","drops","rain","umbrella","weather"],"version":0.6,"annotation":"umbrella with rain drops","shortcodes":["umbrella_with_rain"]},{"emoji":"โ›ฑ๏ธ","group":5,"order":4040,"tags":["ground","rain","sun","umbrella"],"version":0.7,"annotation":"umbrella on ground","shortcodes":["beach_umbrella","umbrella_on_ground"]},{"emoji":"โšก๏ธ","group":5,"order":4041,"tags":["danger","electric","electricity","high","lightning","nature","thunder","thunderbolt","voltage","zap"],"version":0.6,"annotation":"high voltage","shortcodes":["high_voltage","zap"]},{"emoji":"โ„๏ธ","group":5,"order":4043,"tags":["cold","snow","weather"],"version":0.6,"annotation":"snowflake","shortcodes":["snowflake"]},{"emoji":"โ˜ƒ๏ธ","group":5,"order":4045,"tags":["cold","man","snow"],"version":0.7,"annotation":"snowman","shortcodes":["snowman2"]},{"emoji":"โ›„๏ธ","group":5,"order":4046,"tags":["cold","man","snow","snowman"],"version":0.6,"annotation":"snowman without snow","shortcodes":["snowman"]},{"emoji":"โ˜„๏ธ","group":5,"order":4048,"tags":["space"],"version":1,"annotation":"comet","shortcodes":["comet"]},{"emoji":"๐Ÿ”ฅ","group":5,"order":4049,"tags":["af","burn","flame","hot","lit","litaf","tool"],"version":0.6,"annotation":"fire","shortcodes":["fire"]},{"emoji":"๐Ÿ’ง","group":5,"order":4050,"tags":["cold","comic","drop","nature","sad","sweat","tear","water","weather"],"version":0.6,"annotation":"droplet","shortcodes":["droplet"]},{"emoji":"๐ŸŒŠ","group":5,"order":4051,"tags":["nature","ocean","surf","surfer","surfing","water","wave"],"version":0.6,"annotation":"water wave","shortcodes":["ocean","water_wave"]},{"emoji":"๐ŸŽƒ","group":6,"order":4052,"tags":["celebration","halloween","jack","lantern","pumpkin"],"version":0.6,"annotation":"jack-o-lantern","shortcodes":["jack_o_lantern"]},{"emoji":"๐ŸŽ„","group":6,"order":4053,"tags":["celebration","christmas","tree"],"version":0.6,"annotation":"Christmas tree","shortcodes":["christmas_tree"]},{"emoji":"๐ŸŽ†","group":6,"order":4054,"tags":["boom","celebration","entertainment","yolo"],"version":0.6,"annotation":"fireworks","shortcodes":["fireworks"]},{"emoji":"๐ŸŽ‡","group":6,"order":4055,"tags":["boom","celebration","fireworks","sparkle"],"version":0.6,"annotation":"sparkler","shortcodes":["sparkler"]},{"emoji":"๐Ÿงจ","group":6,"order":4056,"tags":["dynamite","explosive","fire","fireworks","light","pop","popping","spark"],"version":11,"annotation":"firecracker","shortcodes":["firecracker"]},{"emoji":"โœจ๏ธ","group":6,"order":4057,"tags":["*","magic","sparkle","star"],"version":0.6,"annotation":"sparkles","shortcodes":["sparkles"]},{"emoji":"๐ŸŽˆ","group":6,"order":4058,"tags":["birthday","celebrate","celebration"],"version":0.6,"annotation":"balloon","shortcodes":["balloon"]},{"emoji":"๐ŸŽ‰","group":6,"order":4059,"tags":["awesome","birthday","celebrate","celebration","excited","hooray","party","popper","tada","woohoo"],"version":0.6,"annotation":"party popper","shortcodes":["party","party_popper","tada"]},{"emoji":"๐ŸŽŠ","group":6,"order":4060,"tags":["ball","celebrate","celebration","confetti","party","woohoo"],"version":0.6,"annotation":"confetti ball","shortcodes":["confetti_ball"]},{"emoji":"๐ŸŽ‹","group":6,"order":4061,"tags":["banner","celebration","japanese","tanabata","tree"],"version":0.6,"annotation":"tanabata tree","shortcodes":["tanabata_tree"]},{"emoji":"๐ŸŽ","group":6,"order":4062,"tags":["bamboo","celebration","decoration","japanese","pine","plant"],"version":0.6,"annotation":"pine decoration","shortcodes":["bamboo"]},{"emoji":"๐ŸŽŽ","group":6,"order":4063,"tags":["celebration","doll","dolls","festival","japanese"],"version":0.6,"annotation":"Japanese dolls","shortcodes":["dolls"]},{"emoji":"๐ŸŽ","group":6,"order":4064,"tags":["carp","celebration","streamer"],"version":0.6,"annotation":"carp streamer","shortcodes":["carp_streamer","flags"]},{"emoji":"๐ŸŽ","group":6,"order":4065,"tags":["bell","celebration","chime","wind"],"version":0.6,"annotation":"wind chime","shortcodes":["wind_chime"]},{"emoji":"๐ŸŽ‘","group":6,"order":4066,"tags":["celebration","ceremony","moon","viewing"],"version":0.6,"annotation":"moon viewing ceremony","shortcodes":["moon_ceremony","rice_scene"]},{"emoji":"๐Ÿงง","group":6,"order":4067,"tags":["envelope","gift","good","hรณngbฤo","lai","luck","money","red","see"],"version":11,"annotation":"red envelope","shortcodes":["red_envelope"]},{"emoji":"๐ŸŽ€","group":6,"order":4068,"tags":["celebration"],"version":0.6,"annotation":"ribbon","shortcodes":["ribbon"]},{"emoji":"๐ŸŽ","group":6,"order":4069,"tags":["birthday","bow","box","celebration","christmas","gift","present","surprise","wrapped"],"version":0.6,"annotation":"wrapped gift","shortcodes":["gift"]},{"emoji":"๐ŸŽ—๏ธ","group":6,"order":4071,"tags":["celebration","reminder","ribbon"],"version":0.7,"annotation":"reminder ribbon","shortcodes":["reminder_ribbon"]},{"emoji":"๐ŸŽŸ๏ธ","group":6,"order":4073,"tags":["admission","ticket","tickets"],"version":0.7,"annotation":"admission tickets","shortcodes":["admission_tickets","tickets"]},{"emoji":"๐ŸŽซ","group":6,"order":4074,"tags":["admission","stub"],"version":0.6,"annotation":"ticket","shortcodes":["ticket"]},{"emoji":"๐ŸŽ–๏ธ","group":6,"order":4076,"tags":["award","celebration","medal","military"],"version":0.7,"annotation":"military medal","shortcodes":["military_medal"]},{"emoji":"๐Ÿ†๏ธ","group":6,"order":4077,"tags":["champion","champs","prize","slay","sport","victory","win","winning"],"version":0.6,"annotation":"trophy","shortcodes":["trophy"]},{"emoji":"๐Ÿ…","group":6,"order":4078,"tags":["award","gold","medal","sports","winner"],"version":1,"annotation":"sports medal","shortcodes":["sports_medal"]},{"emoji":"๐Ÿฅ‡","group":6,"order":4079,"tags":["1st","first","gold","medal","place"],"version":3,"annotation":"1st place medal","shortcodes":["1st","first_place_medal"]},{"emoji":"๐Ÿฅˆ","group":6,"order":4080,"tags":["2nd","medal","place","second","silver"],"version":3,"annotation":"2nd place medal","shortcodes":["2nd","second_place_medal"]},{"emoji":"๐Ÿฅ‰","group":6,"order":4081,"tags":["3rd","bronze","medal","place","third"],"version":3,"annotation":"3rd place medal","shortcodes":["3rd","third_place_medal"]},{"emoji":"โšฝ๏ธ","group":6,"order":4082,"tags":["ball","football","futbol","soccer","sport"],"version":0.6,"annotation":"soccer ball","shortcodes":["soccer"]},{"emoji":"โšพ๏ธ","group":6,"order":4083,"tags":["ball","sport"],"version":0.6,"annotation":"baseball","shortcodes":["baseball"]},{"emoji":"๐ŸฅŽ","group":6,"order":4084,"tags":["ball","glove","sports","underarm"],"version":11,"annotation":"softball","shortcodes":["softball"]},{"emoji":"๐Ÿ€","group":6,"order":4085,"tags":["ball","hoop","sport"],"version":0.6,"annotation":"basketball","shortcodes":["basketball"]},{"emoji":"๐Ÿ","group":6,"order":4086,"tags":["ball","game"],"version":1,"annotation":"volleyball","shortcodes":["volleyball"]},{"emoji":"๐Ÿˆ","group":6,"order":4087,"tags":["american","ball","bowl","football","sport","super"],"version":0.6,"annotation":"american football","shortcodes":["football"]},{"emoji":"๐Ÿ‰","group":6,"order":4088,"tags":["ball","football","rugby","sport"],"version":1,"annotation":"rugby football","shortcodes":["rugby_football"]},{"emoji":"๐ŸŽพ","group":6,"order":4089,"tags":["ball","racquet","sport"],"version":0.6,"annotation":"tennis","shortcodes":["tennis"]},{"emoji":"๐Ÿฅ","group":6,"order":4090,"tags":["disc","flying","ultimate"],"version":11,"annotation":"flying disc","shortcodes":["flying_disc"]},{"emoji":"๐ŸŽณ","group":6,"order":4091,"tags":["ball","game","sport","strike"],"version":0.6,"annotation":"bowling","shortcodes":["bowling"]},{"emoji":"๐Ÿ","group":6,"order":4092,"tags":["ball","bat","cricket","game"],"version":1,"annotation":"cricket game","shortcodes":["cricket_game"]},{"emoji":"๐Ÿ‘","group":6,"order":4093,"tags":["ball","field","game","hockey","stick"],"version":1,"annotation":"field hockey","shortcodes":["field_hockey"]},{"emoji":"๐Ÿ’","group":6,"order":4094,"tags":["game","hockey","ice","puck","stick"],"version":1,"annotation":"ice hockey","shortcodes":["hockey"]},{"emoji":"๐Ÿฅ","group":6,"order":4095,"tags":["ball","goal","sports","stick"],"version":11,"annotation":"lacrosse","shortcodes":["lacrosse"]},{"emoji":"๐Ÿ“","group":6,"order":4096,"tags":["ball","bat","game","paddle","ping","pingpong","pong","table","tennis"],"version":1,"annotation":"ping pong","shortcodes":["ping_pong"]},{"emoji":"๐Ÿธ","group":6,"order":4097,"tags":["birdie","game","racquet","shuttlecock"],"version":1,"annotation":"badminton","shortcodes":["badminton"]},{"emoji":"๐ŸฅŠ","group":6,"order":4098,"tags":["boxing","glove"],"version":3,"annotation":"boxing glove","shortcodes":["boxing_glove"]},{"emoji":"๐Ÿฅ‹","group":6,"order":4099,"tags":["arts","judo","karate","martial","taekwondo","uniform"],"version":3,"annotation":"martial arts uniform","shortcodes":["martial_arts_uniform"]},{"emoji":"๐Ÿฅ…","group":6,"order":4100,"tags":["goal","net"],"version":3,"annotation":"goal net","shortcodes":["goal_net"]},{"emoji":"โ›ณ๏ธ","group":6,"order":4101,"tags":["flag","golf","hole","sport"],"version":0.6,"annotation":"flag in hole","shortcodes":["golf"]},{"emoji":"โ›ธ๏ธ","group":6,"order":4103,"tags":["ice","skate","skating"],"version":0.7,"annotation":"ice skate","shortcodes":["ice_skate"]},{"emoji":"๐ŸŽฃ","group":6,"order":4104,"tags":["entertainment","fish","fishing","pole","sport"],"version":0.6,"annotation":"fishing pole","shortcodes":["fishing_pole","fishing_pole_and_fish"]},{"emoji":"๐Ÿคฟ","group":6,"order":4105,"tags":["diving","mask","scuba","snorkeling"],"version":12,"annotation":"diving mask","shortcodes":["diving_mask"]},{"emoji":"๐ŸŽฝ","group":6,"order":4106,"tags":["athletics","running","sash","shirt"],"version":0.6,"annotation":"running shirt","shortcodes":["running_shirt","running_shirt_with_sash"]},{"emoji":"๐ŸŽฟ","group":6,"order":4107,"tags":["ski","snow","sport"],"version":0.6,"annotation":"skis","shortcodes":["ski"]},{"emoji":"๐Ÿ›ท","group":6,"order":4108,"tags":["luge","sledge","sleigh","snow","toboggan"],"version":5,"annotation":"sled","shortcodes":["sled"]},{"emoji":"๐ŸฅŒ","group":6,"order":4109,"tags":["curling","game","rock","stone"],"version":5,"annotation":"curling stone","shortcodes":["curling_stone"]},{"emoji":"๐ŸŽฏ","group":6,"order":4110,"tags":["bull","dart","direct","entertainment","game","hit","target"],"version":0.6,"annotation":"bullseye","shortcodes":["bullseye","dart","direct_hit"]},{"emoji":"๐Ÿช€","group":6,"order":4111,"tags":["fluctuate","toy"],"version":12,"annotation":"yo-yo","shortcodes":["yo_yo"]},{"emoji":"๐Ÿช","group":6,"order":4112,"tags":["fly","soar"],"version":12,"annotation":"kite","shortcodes":["kite"]},{"emoji":"๐Ÿ”ซ","group":6,"order":4113,"tags":["gun","handgun","pistol","revolver","tool","water","weapon"],"version":0.6,"annotation":"water pistol","shortcodes":["gun","pistol"]},{"emoji":"๐ŸŽฑ","group":6,"order":4114,"tags":["8","8ball","ball","billiard","eight","game","pool"],"version":0.6,"annotation":"pool 8 ball","shortcodes":["8ball","billiards"]},{"emoji":"๐Ÿ”ฎ","group":6,"order":4115,"tags":["ball","crystal","fairy","fairytale","fantasy","fortune","future","magic","tale","tool"],"version":0.6,"annotation":"crystal ball","shortcodes":["crystal_ball"]},{"emoji":"๐Ÿช„","group":6,"order":4116,"tags":["magic","magician","wand","witch","wizard"],"version":13,"annotation":"magic wand","shortcodes":["magic_wand"]},{"emoji":"๐ŸŽฎ๏ธ","group":6,"order":4117,"tags":["controller","entertainment","game","video"],"version":0.6,"annotation":"video game","shortcodes":["controller","video_game"]},{"emoji":"๐Ÿ•น๏ธ","group":6,"order":4119,"tags":["game","video","videogame"],"version":0.7,"annotation":"joystick","shortcodes":["joystick"]},{"emoji":"๐ŸŽฐ","group":6,"order":4120,"tags":["casino","gamble","gambling","game","machine","slot","slots"],"version":0.6,"annotation":"slot machine","shortcodes":["slot_machine"]},{"emoji":"๐ŸŽฒ","group":6,"order":4121,"tags":["dice","die","entertainment","game"],"version":0.6,"annotation":"game die","shortcodes":["game_die"]},{"emoji":"๐Ÿงฉ","group":6,"order":4122,"tags":["clue","interlocking","jigsaw","piece","puzzle"],"version":11,"annotation":"puzzle piece","shortcodes":["jigsaw","puzzle_piece"]},{"emoji":"๐Ÿงธ","group":6,"order":4123,"tags":["bear","plaything","plush","stuffed","teddy","toy"],"version":11,"annotation":"teddy bear","shortcodes":["teddy_bear"]},{"emoji":"๐Ÿช…","group":6,"order":4124,"tags":["candy","celebrate","celebration","cinco","de","festive","mayo","party","pinada","pinata"],"version":13,"annotation":"piรฑata","shortcodes":["pinata"]},{"emoji":"๐Ÿชฉ","group":6,"order":4125,"tags":["ball","dance","disco","glitter","mirror","party"],"version":14,"annotation":"mirror ball","shortcodes":["disco","disco_ball","mirror_ball"]},{"emoji":"๐Ÿช†","group":6,"order":4126,"tags":["babooshka","baboushka","babushka","doll","dolls","matryoshka","nesting","russia"],"version":13,"annotation":"nesting dolls","shortcodes":["nesting_dolls"]},{"emoji":"โ™ ๏ธ","group":6,"order":4128,"tags":["card","game","spade","suit"],"version":0.6,"annotation":"spade suit","shortcodes":["spades"]},{"emoji":"โ™ฅ๏ธ","group":6,"order":4130,"tags":["card","emotion","game","heart","hearts","suit"],"version":0.6,"annotation":"heart suit","shortcodes":["hearts"]},{"emoji":"โ™ฆ๏ธ","group":6,"order":4132,"tags":["card","diamond","game","suit"],"version":0.6,"annotation":"diamond suit","shortcodes":["diamonds"]},{"emoji":"โ™ฃ๏ธ","group":6,"order":4134,"tags":["card","club","clubs","game","suit"],"version":0.6,"annotation":"club suit","shortcodes":["clubs"]},{"emoji":"โ™Ÿ๏ธ","group":6,"order":4136,"tags":["chess","dupe","expendable","pawn"],"version":11,"annotation":"chess pawn","shortcodes":["chess_pawn"]},{"emoji":"๐Ÿƒ","group":6,"order":4137,"tags":["card","game","wildcard"],"version":0.6,"annotation":"joker","shortcodes":["black_joker"]},{"emoji":"๐Ÿ€„๏ธ","group":6,"order":4138,"tags":["dragon","game","mahjong","red"],"version":0.6,"annotation":"mahjong red dragon","shortcodes":["mahjong"]},{"emoji":"๐ŸŽด","group":6,"order":4139,"tags":["card","cards","flower","game","japanese","playing"],"version":0.6,"annotation":"flower playing cards","shortcodes":["flower_playing_cards"]},{"emoji":"๐ŸŽญ๏ธ","group":6,"order":4140,"tags":["actor","actress","art","arts","entertainment","mask","performing","theater","theatre","thespian"],"version":0.6,"annotation":"performing arts","shortcodes":["performing_arts"]},{"emoji":"๐Ÿ–ผ๏ธ","group":6,"order":4142,"tags":["art","frame","framed","museum","painting","picture"],"version":0.7,"annotation":"framed picture","shortcodes":["frame_with_picture","framed_picture"]},{"emoji":"๐ŸŽจ","group":6,"order":4143,"tags":["art","artist","artsy","arty","colorful","creative","entertainment","museum","painter","painting","palette"],"version":0.6,"annotation":"artist palette","shortcodes":["art","palette"]},{"emoji":"๐Ÿงต","group":6,"order":4144,"tags":["needle","sewing","spool","string"],"version":11,"annotation":"thread","shortcodes":["thread"]},{"emoji":"๐Ÿชก","group":6,"order":4145,"tags":["embroidery","needle","sew","sewing","stitches","sutures","tailoring","thread"],"version":13,"annotation":"sewing needle","shortcodes":["sewing_needle"]},{"emoji":"๐Ÿงถ","group":6,"order":4146,"tags":["ball","crochet","knit"],"version":11,"annotation":"yarn","shortcodes":["yarn"]},{"emoji":"๐Ÿชข","group":6,"order":4147,"tags":["cord","rope","tangled","tie","twine","twist"],"version":13,"annotation":"knot","shortcodes":["knot"]},{"emoji":"๐Ÿ‘“๏ธ","group":7,"order":4148,"tags":["clothing","eye","eyeglasses","eyewear"],"version":0.6,"annotation":"glasses","shortcodes":["eyeglasses","glasses"]},{"emoji":"๐Ÿ•ถ๏ธ","group":7,"order":4150,"tags":["dark","eye","eyewear","glasses"],"version":0.7,"annotation":"sunglasses","shortcodes":["sunglasses"]},{"emoji":"๐Ÿฅฝ","group":7,"order":4151,"tags":["dive","eye","protection","scuba","swimming","welding"],"version":11,"annotation":"goggles","shortcodes":["goggles"]},{"emoji":"๐Ÿฅผ","group":7,"order":4152,"tags":["clothes","coat","doctor","dr","experiment","jacket","lab","scientist","white"],"version":11,"annotation":"lab coat","shortcodes":["lab_coat"]},{"emoji":"๐Ÿฆบ","group":7,"order":4153,"tags":["emergency","safety","vest"],"version":12,"annotation":"safety vest","shortcodes":["safety_vest"]},{"emoji":"๐Ÿ‘”","group":7,"order":4154,"tags":["clothing","employed","serious","shirt","tie"],"version":0.6,"annotation":"necktie","shortcodes":["necktie"]},{"emoji":"๐Ÿ‘•","group":7,"order":4155,"tags":["blue","casual","clothes","clothing","collar","dressed","shirt","shopping","tshirt","weekend"],"version":0.6,"annotation":"t-shirt","shortcodes":["shirt"]},{"emoji":"๐Ÿ‘–","group":7,"order":4156,"tags":["blue","casual","clothes","clothing","denim","dressed","pants","shopping","trousers","weekend"],"version":0.6,"annotation":"jeans","shortcodes":["jeans"]},{"emoji":"๐Ÿงฃ","group":7,"order":4157,"tags":["bundle","cold","neck","up"],"version":5,"annotation":"scarf","shortcodes":["scarf"]},{"emoji":"๐Ÿงค","group":7,"order":4158,"tags":["hand"],"version":5,"annotation":"gloves","shortcodes":["gloves"]},{"emoji":"๐Ÿงฅ","group":7,"order":4159,"tags":["brr","bundle","cold","jacket","up"],"version":5,"annotation":"coat","shortcodes":["coat"]},{"emoji":"๐Ÿงฆ","group":7,"order":4160,"tags":["stocking"],"version":5,"annotation":"socks","shortcodes":["socks"]},{"emoji":"๐Ÿ‘—","group":7,"order":4161,"tags":["clothes","clothing","dressed","fancy","shopping"],"version":0.6,"annotation":"dress","shortcodes":["dress"]},{"emoji":"๐Ÿ‘˜","group":7,"order":4162,"tags":["clothing","comfortable"],"version":0.6,"annotation":"kimono","shortcodes":["kimono"]},{"emoji":"๐Ÿฅป","group":7,"order":4163,"tags":["clothing","dress"],"version":12,"annotation":"sari","shortcodes":["sari"]},{"emoji":"๐Ÿฉฑ","group":7,"order":4164,"tags":["bathing","one-piece","suit","swimsuit"],"version":12,"annotation":"one-piece swimsuit","shortcodes":["one_piece_swimsuit"]},{"emoji":"๐Ÿฉฒ","group":7,"order":4165,"tags":["bathing","one-piece","suit","swimsuit","underwear"],"version":12,"annotation":"briefs","shortcodes":["briefs"]},{"emoji":"๐Ÿฉณ","group":7,"order":4166,"tags":["bathing","pants","suit","swimsuit","underwear"],"version":12,"annotation":"shorts","shortcodes":["shorts"]},{"emoji":"๐Ÿ‘™","group":7,"order":4167,"tags":["bathing","beach","clothing","pool","suit","swim"],"version":0.6,"annotation":"bikini","shortcodes":["bikini"]},{"emoji":"๐Ÿ‘š","group":7,"order":4168,"tags":["blouse","clothes","clothing","collar","dress","dressed","lady","shirt","shopping","woman","womanโ€™s"],"version":0.6,"annotation":"womanโ€™s clothes","shortcodes":["womans_clothes"]},{"emoji":"๐Ÿชญ","group":7,"order":4169,"tags":["clack","clap","cool","cooling","dance","fan","flirt","flutter","folding","hand","hot","shy"],"version":15,"annotation":"folding hand fan","shortcodes":["folding_fan"]},{"emoji":"๐Ÿ‘›","group":7,"order":4170,"tags":["clothes","clothing","coin","dress","fancy","handbag","shopping"],"version":0.6,"annotation":"purse","shortcodes":["purse"]},{"emoji":"๐Ÿ‘œ","group":7,"order":4171,"tags":["bag","clothes","clothing","dress","lady","purse","shopping"],"version":0.6,"annotation":"handbag","shortcodes":["handbag"]},{"emoji":"๐Ÿ‘","group":7,"order":4172,"tags":["bag","clothes","clothing","clutch","dress","handbag","pouch","purse"],"version":0.6,"annotation":"clutch bag","shortcodes":["clutch_bag","pouch"]},{"emoji":"๐Ÿ›๏ธ","group":7,"order":4174,"tags":["bag","bags","hotel","shopping"],"version":0.7,"annotation":"shopping bags","shortcodes":["shopping_bags"]},{"emoji":"๐ŸŽ’","group":7,"order":4175,"tags":["backpacking","bag","bookbag","education","rucksack","satchel","school"],"version":0.6,"annotation":"backpack","shortcodes":["backpack","school_satchel"]},{"emoji":"๐Ÿฉด","group":7,"order":4176,"tags":["beach","flip","flop","sandal","sandals","shoe","thong","thongs","zลri"],"version":13,"annotation":"thong sandal","shortcodes":["thong_sandal"]},{"emoji":"๐Ÿ‘ž","group":7,"order":4177,"tags":["brown","clothes","clothing","feet","foot","kick","man","manโ€™s","shoe","shoes","shopping"],"version":0.6,"annotation":"manโ€™s shoe","shortcodes":["mans_shoe"]},{"emoji":"๐Ÿ‘Ÿ","group":7,"order":4178,"tags":["athletic","clothes","clothing","fast","kick","running","shoe","shoes","shopping","sneaker","tennis"],"version":0.6,"annotation":"running shoe","shortcodes":["athletic_shoe","sneaker"]},{"emoji":"๐Ÿฅพ","group":7,"order":4179,"tags":["backpacking","boot","brown","camping","hiking","outdoors","shoe"],"version":11,"annotation":"hiking boot","shortcodes":["hiking_boot"]},{"emoji":"๐Ÿฅฟ","group":7,"order":4180,"tags":["ballet","comfy","flat","flats","shoe","slip-on","slipper"],"version":11,"annotation":"flat shoe","shortcodes":["flat_shoe","womans_flat_shoe"]},{"emoji":"๐Ÿ‘ ","group":7,"order":4181,"tags":["clothes","clothing","dress","fashion","heel","heels","high-heeled","shoe","shoes","shopping","stiletto","woman"],"version":0.6,"annotation":"high-heeled shoe","shortcodes":["high_heel"]},{"emoji":"๐Ÿ‘ก","group":7,"order":4182,"tags":["clothing","sandal","shoe","woman","womanโ€™s"],"version":0.6,"annotation":"womanโ€™s sandal","shortcodes":["sandal"]},{"emoji":"๐Ÿฉฐ","group":7,"order":4183,"tags":["ballet","dance","shoes"],"version":12,"annotation":"ballet shoes","shortcodes":["ballet_shoes"]},{"emoji":"๐Ÿ‘ข","group":7,"order":4184,"tags":["boot","clothes","clothing","dress","shoe","shoes","shopping","woman","womanโ€™s"],"version":0.6,"annotation":"womanโ€™s boot","shortcodes":["boot"]},{"emoji":"๐Ÿชฎ","group":7,"order":4185,"tags":["afro","comb","groom","hair","pick"],"version":15,"annotation":"hair pick","shortcodes":["hair_pick"]},{"emoji":"๐Ÿ‘‘","group":7,"order":4186,"tags":["clothing","family","king","medieval","queen","royal","royalty","win"],"version":0.6,"annotation":"crown","shortcodes":["crown"]},{"emoji":"๐Ÿ‘’","group":7,"order":4187,"tags":["clothes","clothing","garden","hat","hats","party","woman","womanโ€™s"],"version":0.6,"annotation":"womanโ€™s hat","shortcodes":["womans_hat"]},{"emoji":"๐ŸŽฉ","group":7,"order":4188,"tags":["clothes","clothing","fancy","formal","hat","magic","top","tophat"],"version":0.6,"annotation":"top hat","shortcodes":["top_hat","tophat"]},{"emoji":"๐ŸŽ“๏ธ","group":7,"order":4189,"tags":["cap","celebration","clothing","education","graduation","hat","scholar"],"version":0.6,"annotation":"graduation cap","shortcodes":["graduation_cap","mortar_board"]},{"emoji":"๐Ÿงข","group":7,"order":4190,"tags":["baseball","bent","billed","cap","dad","hat"],"version":5,"annotation":"billed cap","shortcodes":["billed_cap"]},{"emoji":"๐Ÿช–","group":7,"order":4191,"tags":["army","helmet","military","soldier","war","warrior"],"version":13,"annotation":"military helmet","shortcodes":["military_helmet"]},{"emoji":"โ›‘๏ธ","group":7,"order":4193,"tags":["aid","cross","face","hat","helmet","rescue","workerโ€™s"],"version":0.7,"annotation":"rescue workerโ€™s helmet","shortcodes":["helmet_with_cross","rescue_worker_helmet"]},{"emoji":"๐Ÿ“ฟ","group":7,"order":4194,"tags":["beads","clothing","necklace","prayer","religion"],"version":1,"annotation":"prayer beads","shortcodes":["prayer_beads"]},{"emoji":"๐Ÿ’„","group":7,"order":4195,"tags":["cosmetics","date","makeup"],"version":0.6,"annotation":"lipstick","shortcodes":["lipstick"]},{"emoji":"๐Ÿ’","group":7,"order":4196,"tags":["diamond","engaged","engagement","married","romance","shiny","sparkling","wedding"],"version":0.6,"annotation":"ring","shortcodes":["ring"]},{"emoji":"๐Ÿ’Ž","group":7,"order":4197,"tags":["diamond","engagement","gem","jewel","money","romance","stone","wedding"],"version":0.6,"annotation":"gem stone","shortcodes":["gem"]},{"emoji":"๐Ÿ”‡","group":7,"order":4198,"tags":["mute","muted","quiet","silent","sound","speaker"],"version":1,"annotation":"muted speaker","shortcodes":["mute","no_sound"]},{"emoji":"๐Ÿ”ˆ๏ธ","group":7,"order":4199,"tags":["low","soft","sound","speaker","volume"],"version":0.7,"annotation":"speaker low volume","shortcodes":["low_volume","quiet_sound","speaker"]},{"emoji":"๐Ÿ”‰","group":7,"order":4200,"tags":["medium","sound","speaker","volume"],"version":1,"annotation":"speaker medium volume","shortcodes":["medium_volumne","sound"]},{"emoji":"๐Ÿ”Š","group":7,"order":4201,"tags":["high","loud","music","sound","speaker","volume"],"version":0.6,"annotation":"speaker high volume","shortcodes":["high_volume","loud_sound"]},{"emoji":"๐Ÿ“ข","group":7,"order":4202,"tags":["address","communication","loud","public","sound"],"version":0.6,"annotation":"loudspeaker","shortcodes":["loudspeaker"]},{"emoji":"๐Ÿ“ฃ","group":7,"order":4203,"tags":["cheering","sound"],"version":0.6,"annotation":"megaphone","shortcodes":["mega","megaphone"]},{"emoji":"๐Ÿ“ฏ","group":7,"order":4204,"tags":["horn","post","postal"],"version":1,"annotation":"postal horn","shortcodes":["postal_horn"]},{"emoji":"๐Ÿ””","group":7,"order":4205,"tags":["break","church","sound"],"version":0.6,"annotation":"bell","shortcodes":["bell"]},{"emoji":"๐Ÿ”•","group":7,"order":4206,"tags":["bell","forbidden","mute","no","not","prohibited","quiet","silent","slash","sound"],"version":1,"annotation":"bell with slash","shortcodes":["no_bell"]},{"emoji":"๐ŸŽผ","group":7,"order":4207,"tags":["music","musical","note","score"],"version":0.6,"annotation":"musical score","shortcodes":["musical_score"]},{"emoji":"๐ŸŽต","group":7,"order":4208,"tags":["music","musical","note","sound"],"version":0.6,"annotation":"musical note","shortcodes":["musical_note"]},{"emoji":"๐ŸŽถ","group":7,"order":4209,"tags":["music","musical","note","notes","sound"],"version":0.6,"annotation":"musical notes","shortcodes":["musical_notes","notes"]},{"emoji":"๐ŸŽ™๏ธ","group":7,"order":4211,"tags":["mic","microphone","music","studio"],"version":0.7,"annotation":"studio microphone","shortcodes":["studio_microphone"]},{"emoji":"๐ŸŽš๏ธ","group":7,"order":4213,"tags":["level","music","slider"],"version":0.7,"annotation":"level slider","shortcodes":["level_slider"]},{"emoji":"๐ŸŽ›๏ธ","group":7,"order":4215,"tags":["control","knobs","music"],"version":0.7,"annotation":"control knobs","shortcodes":["control_knobs"]},{"emoji":"๐ŸŽค","group":7,"order":4216,"tags":["karaoke","mic","music","sing","sound"],"version":0.6,"annotation":"microphone","shortcodes":["microphone"]},{"emoji":"๐ŸŽง๏ธ","group":7,"order":4217,"tags":["earbud","sound"],"version":0.6,"annotation":"headphone","shortcodes":["headphones"]},{"emoji":"๐Ÿ“ป๏ธ","group":7,"order":4218,"tags":["entertainment","tbt","video"],"version":0.6,"annotation":"radio","shortcodes":["radio"]},{"emoji":"๐ŸŽท","group":7,"order":4219,"tags":["instrument","music","sax"],"version":0.6,"annotation":"saxophone","shortcodes":["saxophone"]},{"emoji":"๐Ÿช—","group":7,"order":4220,"tags":["box","concertina","instrument","music","squeeze","squeezebox"],"version":13,"annotation":"accordion","shortcodes":["accordion"]},{"emoji":"๐ŸŽธ","group":7,"order":4221,"tags":["instrument","music","strat"],"version":0.6,"annotation":"guitar","shortcodes":["guitar"]},{"emoji":"๐ŸŽน","group":7,"order":4222,"tags":["instrument","keyboard","music","musical","piano"],"version":0.6,"annotation":"musical keyboard","shortcodes":["musical_keyboard"]},{"emoji":"๐ŸŽบ","group":7,"order":4223,"tags":["instrument","music"],"version":0.6,"annotation":"trumpet","shortcodes":["trumpet"]},{"emoji":"๐ŸŽป","group":7,"order":4224,"tags":["instrument","music"],"version":0.6,"annotation":"violin","shortcodes":["violin"]},{"emoji":"๐Ÿช•","group":7,"order":4225,"tags":["music","stringed"],"version":12,"annotation":"banjo","shortcodes":["banjo"]},{"emoji":"๐Ÿฅ","group":7,"order":4226,"tags":["drumsticks","music"],"version":3,"annotation":"drum","shortcodes":["drum"]},{"emoji":"๐Ÿช˜","group":7,"order":4227,"tags":["beat","conga","drum","instrument","long","rhythm"],"version":13,"annotation":"long drum","shortcodes":["long_drum"]},{"emoji":"๐Ÿช‡","group":7,"order":4228,"tags":["cha","dance","instrument","music","party","percussion","rattle","shake","shaker"],"version":15,"annotation":"maracas","shortcodes":["maracas"]},{"emoji":"๐Ÿชˆ","group":7,"order":4229,"tags":["band","fife","flautist","instrument","marching","music","orchestra","piccolo","pipe","recorder","woodwind"],"version":15,"annotation":"flute","shortcodes":["flute"]},{"emoji":"๐Ÿช‰","group":7,"order":4230,"tags":["cupid","instrument","love","music","orchestra"],"version":16,"annotation":"harp","shortcodes":["harp"]},{"emoji":"๐Ÿ“ฑ","group":7,"order":4231,"tags":["cell","communication","mobile","phone","telephone"],"version":0.6,"annotation":"mobile phone","shortcodes":["android","iphone","mobile_phone"]},{"emoji":"๐Ÿ“ฒ","group":7,"order":4232,"tags":["arrow","build","call","cell","communication","mobile","phone","receive","telephone"],"version":0.6,"annotation":"mobile phone with arrow","shortcodes":["calling","mobile_phone_arrow"]},{"emoji":"โ˜Ž๏ธ","group":7,"order":4234,"tags":["phone"],"version":0.6,"annotation":"telephone","shortcodes":["telephone"]},{"emoji":"๐Ÿ“ž","group":7,"order":4235,"tags":["communication","phone","receiver","telephone","voip"],"version":0.6,"annotation":"telephone receiver","shortcodes":["telephone_receiver"]},{"emoji":"๐Ÿ“Ÿ๏ธ","group":7,"order":4236,"tags":["communication"],"version":0.6,"annotation":"pager","shortcodes":["pager"]},{"emoji":"๐Ÿ“ ","group":7,"order":4237,"tags":["communication","fax","machine"],"version":0.6,"annotation":"fax machine","shortcodes":["fax","fax_machine"]},{"emoji":"๐Ÿ”‹","group":7,"order":4238,"tags":["battery"],"version":0.6,"annotation":"battery","shortcodes":["battery"]},{"emoji":"๐Ÿชซ","group":7,"order":4239,"tags":["battery","drained","electronic","energy","low","power"],"version":14,"annotation":"low battery","shortcodes":["low_battery"]},{"emoji":"๐Ÿ”Œ","group":7,"order":4240,"tags":["electric","electricity","plug"],"version":0.6,"annotation":"electric plug","shortcodes":["electric_plug"]},{"emoji":"๐Ÿ’ป๏ธ","group":7,"order":4241,"tags":["computer","office","pc","personal"],"version":0.6,"annotation":"laptop","shortcodes":["laptop"]},{"emoji":"๐Ÿ–ฅ๏ธ","group":7,"order":4243,"tags":["computer","desktop","monitor"],"version":0.7,"annotation":"desktop computer","shortcodes":["computer","desktop_computer"]},{"emoji":"๐Ÿ–จ๏ธ","group":7,"order":4245,"tags":["computer"],"version":0.7,"annotation":"printer","shortcodes":["printer"]},{"emoji":"โŒจ๏ธ","group":7,"order":4247,"tags":["computer"],"version":1,"annotation":"keyboard","shortcodes":["keyboard"]},{"emoji":"๐Ÿ–ฑ๏ธ","group":7,"order":4249,"tags":["computer","mouse"],"version":0.7,"annotation":"computer mouse","shortcodes":["computer_mouse"]},{"emoji":"๐Ÿ–ฒ๏ธ","group":7,"order":4251,"tags":["computer"],"version":0.7,"annotation":"trackball","shortcodes":["trackball"]},{"emoji":"๐Ÿ’ฝ","group":7,"order":4252,"tags":["computer","disk","minidisk","optical"],"version":0.6,"annotation":"computer disk","shortcodes":["computer_disk","minidisc"]},{"emoji":"๐Ÿ’พ","group":7,"order":4253,"tags":["computer","disk","floppy"],"version":0.6,"annotation":"floppy disk","shortcodes":["floppy_disk"]},{"emoji":"๐Ÿ’ฟ๏ธ","group":7,"order":4254,"tags":["blu-ray","cd","computer","disk","dvd","optical"],"version":0.6,"annotation":"optical disk","shortcodes":["cd","optical_disk"]},{"emoji":"๐Ÿ“€","group":7,"order":4255,"tags":["blu-ray","cd","computer","disk","optical"],"version":0.6,"annotation":"dvd","shortcodes":["dvd"]},{"emoji":"๐Ÿงฎ","group":7,"order":4256,"tags":["calculation","calculator"],"version":11,"annotation":"abacus","shortcodes":["abacus"]},{"emoji":"๐ŸŽฅ","group":7,"order":4257,"tags":["bollywood","camera","cinema","film","hollywood","movie","record"],"version":0.6,"annotation":"movie camera","shortcodes":["movie_camera"]},{"emoji":"๐ŸŽž๏ธ","group":7,"order":4259,"tags":["cinema","film","frames","movie"],"version":0.7,"annotation":"film frames","shortcodes":["film_frames"]},{"emoji":"๐Ÿ“ฝ๏ธ","group":7,"order":4261,"tags":["cinema","film","movie","projector","video"],"version":0.7,"annotation":"film projector","shortcodes":["film_projector"]},{"emoji":"๐ŸŽฌ๏ธ","group":7,"order":4262,"tags":["action","board","clapper","movie"],"version":0.6,"annotation":"clapper board","shortcodes":["clapper"]},{"emoji":"๐Ÿ“บ๏ธ","group":7,"order":4263,"tags":["tv","video"],"version":0.6,"annotation":"television","shortcodes":["tv"]},{"emoji":"๐Ÿ“ท๏ธ","group":7,"order":4264,"tags":["photo","selfie","snap","tbt","trip","video"],"version":0.6,"annotation":"camera","shortcodes":["camera"]},{"emoji":"๐Ÿ“ธ","group":7,"order":4265,"tags":["camera","flash","video"],"version":1,"annotation":"camera with flash","shortcodes":["camera_with_flash"]},{"emoji":"๐Ÿ“น๏ธ","group":7,"order":4266,"tags":["camcorder","camera","tbt","video"],"version":0.6,"annotation":"video camera","shortcodes":["video_camera"]},{"emoji":"๐Ÿ“ผ","group":7,"order":4267,"tags":["old","school","tape","vcr","vhs","video"],"version":0.6,"annotation":"videocassette","shortcodes":["vhs","videocassette"]},{"emoji":"๐Ÿ”๏ธ","group":7,"order":4268,"tags":["glass","lab","left","left-pointing","magnifying","science","search","tilted","tool"],"version":0.6,"annotation":"magnifying glass tilted left","shortcodes":["mag"]},{"emoji":"๐Ÿ”Ž","group":7,"order":4269,"tags":["contact","glass","lab","magnifying","right","right-pointing","science","search","tilted","tool"],"version":0.6,"annotation":"magnifying glass tilted right","shortcodes":["mag_right"]},{"emoji":"๐Ÿ•ฏ๏ธ","group":7,"order":4271,"tags":["light"],"version":0.7,"annotation":"candle","shortcodes":["candle"]},{"emoji":"๐Ÿ’ก","group":7,"order":4272,"tags":["bulb","comic","electric","idea","light"],"version":0.6,"annotation":"light bulb","shortcodes":["bulb","light_bulb"]},{"emoji":"๐Ÿ”ฆ","group":7,"order":4273,"tags":["electric","light","tool","torch"],"version":0.6,"annotation":"flashlight","shortcodes":["flashlight"]},{"emoji":"๐Ÿฎ","group":7,"order":4274,"tags":["bar","lantern","light","paper","red","restaurant"],"version":0.6,"annotation":"red paper lantern","shortcodes":["izakaya_lantern","red_paper_lantern"]},{"emoji":"๐Ÿช”","group":7,"order":4275,"tags":["diya","lamp","light","oil"],"version":12,"annotation":"diya lamp","shortcodes":["diya_lamp"]},{"emoji":"๐Ÿ“”","group":7,"order":4276,"tags":["book","cover","decorated","decorative","education","notebook","school","writing"],"version":0.6,"annotation":"notebook with decorative cover","shortcodes":["notebook_with_decorative_cover"]},{"emoji":"๐Ÿ“•","group":7,"order":4277,"tags":["book","closed","education"],"version":0.6,"annotation":"closed book","shortcodes":["closed_book"]},{"emoji":"๐Ÿ“–","group":7,"order":4278,"tags":["book","education","fantasy","knowledge","library","novels","open","reading"],"version":0.6,"annotation":"open book","shortcodes":["book","open_book"]},{"emoji":"๐Ÿ“—","group":7,"order":4279,"tags":["book","education","fantasy","green","library","reading"],"version":0.6,"annotation":"green book","shortcodes":["green_book"]},{"emoji":"๐Ÿ“˜","group":7,"order":4280,"tags":["blue","book","education","fantasy","library","reading"],"version":0.6,"annotation":"blue book","shortcodes":["blue_book"]},{"emoji":"๐Ÿ“™","group":7,"order":4281,"tags":["book","education","fantasy","library","orange","reading"],"version":0.6,"annotation":"orange book","shortcodes":["orange_book"]},{"emoji":"๐Ÿ“š๏ธ","group":7,"order":4282,"tags":["book","education","fantasy","knowledge","library","novels","reading","school","study"],"version":0.6,"annotation":"books","shortcodes":["books"]},{"emoji":"๐Ÿ““","group":7,"order":4283,"tags":["notebook"],"version":0.6,"annotation":"notebook","shortcodes":["notebook"]},{"emoji":"๐Ÿ“’","group":7,"order":4284,"tags":["notebook"],"version":0.6,"annotation":"ledger","shortcodes":["ledger"]},{"emoji":"๐Ÿ“ƒ","group":7,"order":4285,"tags":["curl","document","page","paper"],"version":0.6,"annotation":"page with curl","shortcodes":["page_with_curl"]},{"emoji":"๐Ÿ“œ","group":7,"order":4286,"tags":["paper"],"version":0.6,"annotation":"scroll","shortcodes":["scroll"]},{"emoji":"๐Ÿ“„","group":7,"order":4287,"tags":["document","facing","page","paper","up"],"version":0.6,"annotation":"page facing up","shortcodes":["page_facing_up"]},{"emoji":"๐Ÿ“ฐ","group":7,"order":4288,"tags":["communication","news","paper"],"version":0.6,"annotation":"newspaper","shortcodes":["newspaper"]},{"emoji":"๐Ÿ—ž๏ธ","group":7,"order":4290,"tags":["news","newspaper","paper","rolled","rolled-up"],"version":0.7,"annotation":"rolled-up newspaper","shortcodes":["rolled_up_newspaper"]},{"emoji":"๐Ÿ“‘","group":7,"order":4291,"tags":["bookmark","mark","marker","tabs"],"version":0.6,"annotation":"bookmark tabs","shortcodes":["bookmark_tabs"]},{"emoji":"๐Ÿ”–","group":7,"order":4292,"tags":["mark"],"version":0.6,"annotation":"bookmark","shortcodes":["bookmark"]},{"emoji":"๐Ÿท๏ธ","group":7,"order":4294,"tags":["tag"],"version":0.7,"annotation":"label","shortcodes":["label"]},{"emoji":"๐Ÿ’ฐ๏ธ","group":7,"order":4295,"tags":["bag","bank","bet","billion","cash","cost","dollar","gold","million","money","moneybag","paid","paying","pot","rich","win"],"version":0.6,"annotation":"money bag","shortcodes":["moneybag"]},{"emoji":"๐Ÿช™","group":7,"order":4296,"tags":["dollar","euro","gold","metal","money","rich","silver","treasure"],"version":13,"annotation":"coin","shortcodes":["coin"]},{"emoji":"๐Ÿ’ด","group":7,"order":4297,"tags":["bank","banknote","bill","currency","money","note","yen"],"version":0.6,"annotation":"yen banknote","shortcodes":["yen"]},{"emoji":"๐Ÿ’ต","group":7,"order":4298,"tags":["bank","banknote","bill","currency","dollar","money","note"],"version":0.6,"annotation":"dollar banknote","shortcodes":["dollar"]},{"emoji":"๐Ÿ’ถ","group":7,"order":4299,"tags":["100","bank","banknote","bill","currency","euro","money","note","rich"],"version":1,"annotation":"euro banknote","shortcodes":["euro"]},{"emoji":"๐Ÿ’ท","group":7,"order":4300,"tags":["bank","banknote","bill","billion","cash","currency","money","note","pound","pounds"],"version":1,"annotation":"pound banknote","shortcodes":["pound"]},{"emoji":"๐Ÿ’ธ","group":7,"order":4301,"tags":["bank","banknote","bill","billion","cash","dollar","fly","million","money","note","pay","wings"],"version":0.6,"annotation":"money with wings","shortcodes":["money_with_wings"]},{"emoji":"๐Ÿ’ณ๏ธ","group":7,"order":4302,"tags":["bank","card","cash","charge","credit","money","pay"],"version":0.6,"annotation":"credit card","shortcodes":["credit_card"]},{"emoji":"๐Ÿงพ","group":7,"order":4303,"tags":["accounting","bookkeeping","evidence","invoice","proof"],"version":11,"annotation":"receipt","shortcodes":["receipt"]},{"emoji":"๐Ÿ’น","group":7,"order":4304,"tags":["bank","chart","currency","graph","growth","increasing","market","money","rise","trend","upward","yen"],"version":0.6,"annotation":"chart increasing with yen","shortcodes":["chart"]},{"emoji":"โœ‰๏ธ","group":7,"order":4306,"tags":["e-mail","email","letter"],"version":0.6,"annotation":"envelope","shortcodes":["envelope"]},{"emoji":"๐Ÿ“ง","group":7,"order":4307,"tags":["email","letter","mail"],"version":0.6,"annotation":"e-mail","shortcodes":["e-mail","email"]},{"emoji":"๐Ÿ“จ","group":7,"order":4308,"tags":["delivering","e-mail","email","envelope","incoming","letter","mail","receive","sent"],"version":0.6,"annotation":"incoming envelope","shortcodes":["incoming_envelope"]},{"emoji":"๐Ÿ“ฉ","group":7,"order":4309,"tags":["arrow","communication","down","e-mail","email","envelope","letter","mail","outgoing","send","sent"],"version":0.6,"annotation":"envelope with arrow","shortcodes":["envelope_with_arrow"]},{"emoji":"๐Ÿ“ค๏ธ","group":7,"order":4310,"tags":["box","email","letter","mail","outbox","sent","tray"],"version":0.6,"annotation":"outbox tray","shortcodes":["outbox_tray"]},{"emoji":"๐Ÿ“ฅ๏ธ","group":7,"order":4311,"tags":["box","email","inbox","letter","mail","receive","tray","zero"],"version":0.6,"annotation":"inbox tray","shortcodes":["inbox_tray"]},{"emoji":"๐Ÿ“ฆ๏ธ","group":7,"order":4312,"tags":["box","communication","delivery","parcel","shipping"],"version":0.6,"annotation":"package","shortcodes":["package"]},{"emoji":"๐Ÿ“ซ๏ธ","group":7,"order":4313,"tags":["closed","communication","flag","mail","mailbox","postbox","raised"],"version":0.6,"annotation":"closed mailbox with raised flag","shortcodes":["mailbox"]},{"emoji":"๐Ÿ“ช๏ธ","group":7,"order":4314,"tags":["closed","flag","lowered","mail","mailbox","postbox"],"version":0.6,"annotation":"closed mailbox with lowered flag","shortcodes":["mailbox_closed"]},{"emoji":"๐Ÿ“ฌ๏ธ","group":7,"order":4315,"tags":["flag","mail","mailbox","open","postbox","raised"],"version":0.7,"annotation":"open mailbox with raised flag","shortcodes":["mailbox_with_mail"]},{"emoji":"๐Ÿ“ญ๏ธ","group":7,"order":4316,"tags":["flag","lowered","mail","mailbox","open","postbox"],"version":0.7,"annotation":"open mailbox with lowered flag","shortcodes":["mailbox_with_no_mail"]},{"emoji":"๐Ÿ“ฎ","group":7,"order":4317,"tags":["mail","mailbox"],"version":0.6,"annotation":"postbox","shortcodes":["postbox"]},{"emoji":"๐Ÿ—ณ๏ธ","group":7,"order":4319,"tags":["ballot","box"],"version":0.7,"annotation":"ballot box with ballot","shortcodes":["ballot_box"]},{"emoji":"โœ๏ธ","group":7,"order":4321,"tags":["pencil"],"version":0.6,"annotation":"pencil","shortcodes":["pencil"]},{"emoji":"โœ’๏ธ","group":7,"order":4323,"tags":["black","nib","pen"],"version":0.6,"annotation":"black nib","shortcodes":["black_nib"]},{"emoji":"๐Ÿ–‹๏ธ","group":7,"order":4325,"tags":["fountain","pen"],"version":0.7,"annotation":"fountain pen","shortcodes":["fountain_pen"]},{"emoji":"๐Ÿ–Š๏ธ","group":7,"order":4327,"tags":["ballpoint"],"version":0.7,"annotation":"pen","shortcodes":["pen"]},{"emoji":"๐Ÿ–Œ๏ธ","group":7,"order":4329,"tags":["painting"],"version":0.7,"annotation":"paintbrush","shortcodes":["paintbrush"]},{"emoji":"๐Ÿ–๏ธ","group":7,"order":4331,"tags":["crayon"],"version":0.7,"annotation":"crayon","shortcodes":["crayon"]},{"emoji":"๐Ÿ“","group":7,"order":4332,"tags":["communication","media","notes","pencil"],"version":0.6,"annotation":"memo","shortcodes":["memo"]},{"emoji":"๐Ÿ’ผ","group":7,"order":4333,"tags":["office"],"version":0.6,"annotation":"briefcase","shortcodes":["briefcase"]},{"emoji":"๐Ÿ“","group":7,"order":4334,"tags":["file","folder"],"version":0.6,"annotation":"file folder","shortcodes":["file_folder"]},{"emoji":"๐Ÿ“‚","group":7,"order":4335,"tags":["file","folder","open"],"version":0.6,"annotation":"open file folder","shortcodes":["open_file_folder"]},{"emoji":"๐Ÿ—‚๏ธ","group":7,"order":4337,"tags":["card","dividers","index"],"version":0.7,"annotation":"card index dividers","shortcodes":["card_index_dividers"]},{"emoji":"๐Ÿ“…","group":7,"order":4338,"tags":["date"],"version":0.6,"annotation":"calendar","shortcodes":["date"]},{"emoji":"๐Ÿ“†","group":7,"order":4339,"tags":["calendar","tear-off"],"version":0.6,"annotation":"tear-off calendar","shortcodes":["calendar"]},{"emoji":"๐Ÿ—’๏ธ","group":7,"order":4341,"tags":["note","notepad","pad","spiral"],"version":0.7,"annotation":"spiral notepad","shortcodes":["notepad_spiral"]},{"emoji":"๐Ÿ—“๏ธ","group":7,"order":4343,"tags":["calendar","pad","spiral"],"version":0.7,"annotation":"spiral calendar","shortcodes":["calendar_spiral"]},{"emoji":"๐Ÿ“‡","group":7,"order":4344,"tags":["card","index","old","rolodex","school"],"version":0.6,"annotation":"card index","shortcodes":["card_index"]},{"emoji":"๐Ÿ“ˆ","group":7,"order":4345,"tags":["chart","data","graph","growth","increasing","right","trend","up","upward"],"version":0.6,"annotation":"chart increasing","shortcodes":["chart_increasing","chart_with_upwards_trend"]},{"emoji":"๐Ÿ“‰","group":7,"order":4346,"tags":["chart","data","decreasing","down","downward","graph","negative","trend"],"version":0.6,"annotation":"chart decreasing","shortcodes":["chart_decreasing","chart_with_downwards_trend"]},{"emoji":"๐Ÿ“Š","group":7,"order":4347,"tags":["bar","chart","data","graph"],"version":0.6,"annotation":"bar chart","shortcodes":["bar_chart"]},{"emoji":"๐Ÿ“‹๏ธ","group":7,"order":4348,"tags":["do","list","notes"],"version":0.6,"annotation":"clipboard","shortcodes":["clipboard"]},{"emoji":"๐Ÿ“Œ","group":7,"order":4349,"tags":["collage","pin"],"version":0.6,"annotation":"pushpin","shortcodes":["pushpin"]},{"emoji":"๐Ÿ“","group":7,"order":4350,"tags":["location","map","pin","pushpin","round"],"version":0.6,"annotation":"round pushpin","shortcodes":["round_pushpin"]},{"emoji":"๐Ÿ“Ž","group":7,"order":4351,"tags":["paperclip"],"version":0.6,"annotation":"paperclip","shortcodes":["paperclip"]},{"emoji":"๐Ÿ–‡๏ธ","group":7,"order":4353,"tags":["link","linked","paperclip","paperclips"],"version":0.7,"annotation":"linked paperclips","shortcodes":["paperclips"]},{"emoji":"๐Ÿ“","group":7,"order":4354,"tags":["angle","edge","math","ruler","straight","straightedge"],"version":0.6,"annotation":"straight ruler","shortcodes":["straight_ruler"]},{"emoji":"๐Ÿ“","group":7,"order":4355,"tags":["angle","math","rule","ruler","set","slide","triangle","triangular"],"version":0.6,"annotation":"triangular ruler","shortcodes":["triangular_ruler"]},{"emoji":"โœ‚๏ธ","group":7,"order":4357,"tags":["cut","cutting","paper","tool"],"version":0.6,"annotation":"scissors","shortcodes":["scissors"]},{"emoji":"๐Ÿ—ƒ๏ธ","group":7,"order":4359,"tags":["box","card","file"],"version":0.7,"annotation":"card file box","shortcodes":["card_file_box"]},{"emoji":"๐Ÿ—„๏ธ","group":7,"order":4361,"tags":["cabinet","file","filing","paper"],"version":0.7,"annotation":"file cabinet","shortcodes":["file_cabinet"]},{"emoji":"๐Ÿ—‘๏ธ","group":7,"order":4363,"tags":["can","garbage","trash","waste"],"version":0.7,"annotation":"wastebasket","shortcodes":["trashcan","wastebasket"]},{"emoji":"๐Ÿ”’๏ธ","group":7,"order":4364,"tags":["closed","lock","private"],"version":0.6,"annotation":"locked","shortcodes":["lock","locked"]},{"emoji":"๐Ÿ”“๏ธ","group":7,"order":4365,"tags":["cracked","lock","open","unlock"],"version":0.6,"annotation":"unlocked","shortcodes":["unlock","unlocked"]},{"emoji":"๐Ÿ”","group":7,"order":4366,"tags":["ink","lock","locked","nib","pen","privacy"],"version":0.6,"annotation":"locked with pen","shortcodes":["lock_with_ink_pen","locked_with_pen"]},{"emoji":"๐Ÿ”","group":7,"order":4367,"tags":["bike","closed","key","lock","locked","secure"],"version":0.6,"annotation":"locked with key","shortcodes":["closed_lock_with_key","locked_with_key"]},{"emoji":"๐Ÿ”‘","group":7,"order":4368,"tags":["keys","lock","major","password","unlock"],"version":0.6,"annotation":"key","shortcodes":["key"]},{"emoji":"๐Ÿ—๏ธ","group":7,"order":4370,"tags":["clue","key","lock","old"],"version":0.7,"annotation":"old key","shortcodes":["old_key"]},{"emoji":"๐Ÿ”จ","group":7,"order":4371,"tags":["home","improvement","repairs","tool"],"version":0.6,"annotation":"hammer","shortcodes":["hammer"]},{"emoji":"๐Ÿช“","group":7,"order":4372,"tags":["ax","chop","hatchet","split","wood"],"version":12,"annotation":"axe","shortcodes":["axe"]},{"emoji":"โ›๏ธ","group":7,"order":4374,"tags":["hammer","mining","tool"],"version":0.7,"annotation":"pick","shortcodes":["pick"]},{"emoji":"โš’๏ธ","group":7,"order":4376,"tags":["hammer","pick","tool"],"version":1,"annotation":"hammer and pick","shortcodes":["hammer_and_pick"]},{"emoji":"๐Ÿ› ๏ธ","group":7,"order":4378,"tags":["hammer","spanner","tool","wrench"],"version":0.7,"annotation":"hammer and wrench","shortcodes":["hammer_and_wrench"]},{"emoji":"๐Ÿ—ก๏ธ","group":7,"order":4380,"tags":["knife","weapon"],"version":0.7,"annotation":"dagger","shortcodes":["dagger"]},{"emoji":"โš”๏ธ","group":7,"order":4382,"tags":["crossed","swords","weapon"],"version":1,"annotation":"crossed swords","shortcodes":["crossed_swords"]},{"emoji":"๐Ÿ’ฃ๏ธ","group":7,"order":4383,"tags":["boom","comic","dangerous","explosion","hot"],"version":0.6,"annotation":"bomb","shortcodes":["bomb"]},{"emoji":"๐Ÿชƒ","group":7,"order":4384,"tags":["rebound","repercussion","weapon"],"version":13,"annotation":"boomerang","shortcodes":["boomerang"]},{"emoji":"๐Ÿน","group":7,"order":4385,"tags":["archer","archery","arrow","bow","sagittarius","tool","weapon","zodiac"],"version":1,"annotation":"bow and arrow","shortcodes":["bow_and_arrow"]},{"emoji":"๐Ÿ›ก๏ธ","group":7,"order":4387,"tags":["weapon"],"version":0.7,"annotation":"shield","shortcodes":["shield"]},{"emoji":"๐Ÿชš","group":7,"order":4388,"tags":["carpenter","carpentry","cut","lumber","saw","tool","trim"],"version":13,"annotation":"carpentry saw","shortcodes":["carpentry_saw"]},{"emoji":"๐Ÿ”ง","group":7,"order":4389,"tags":["home","improvement","spanner","tool"],"version":0.6,"annotation":"wrench","shortcodes":["wrench"]},{"emoji":"๐Ÿช›","group":7,"order":4390,"tags":["flathead","handy","screw","tool"],"version":13,"annotation":"screwdriver","shortcodes":["screwdriver"]},{"emoji":"๐Ÿ”ฉ","group":7,"order":4391,"tags":["bolt","home","improvement","nut","tool"],"version":0.6,"annotation":"nut and bolt","shortcodes":["nut_and_bolt"]},{"emoji":"โš™๏ธ","group":7,"order":4393,"tags":["cog","cogwheel","tool"],"version":1,"annotation":"gear","shortcodes":["gear"]},{"emoji":"๐Ÿ—œ๏ธ","group":7,"order":4395,"tags":["compress","tool","vice"],"version":0.7,"annotation":"clamp","shortcodes":["clamp","compression"]},{"emoji":"โš–๏ธ","group":7,"order":4397,"tags":["balance","justice","libra","scale","scales","tool","weight","zodiac"],"version":1,"annotation":"balance scale","shortcodes":["scales"]},{"emoji":"๐Ÿฆฏ","group":7,"order":4398,"tags":["accessibility","blind","cane","probing","white"],"version":12,"annotation":"white cane","shortcodes":["probing_cane","white_cane"]},{"emoji":"๐Ÿ”—","group":7,"order":4399,"tags":["links"],"version":0.6,"annotation":"link","shortcodes":["link"]},{"emoji":"โ›“๏ธโ€๐Ÿ’ฅ","group":7,"order":4400,"tags":["break","breaking","broken","chain","cuffs","freedom"],"version":15.1,"annotation":"broken chain","shortcodes":["broken_chain"]},{"emoji":"โ›“๏ธ","group":7,"order":4403,"tags":["chain"],"version":0.7,"annotation":"chains","shortcodes":["chains"]},{"emoji":"๐Ÿช","group":7,"order":4404,"tags":["catch","crook","curve","ensnare","point","selling"],"version":13,"annotation":"hook","shortcodes":["hook"]},{"emoji":"๐Ÿงฐ","group":7,"order":4405,"tags":["box","chest","mechanic","red","tool"],"version":11,"annotation":"toolbox","shortcodes":["toolbox"]},{"emoji":"๐Ÿงฒ","group":7,"order":4406,"tags":["attraction","horseshoe","magnetic","negative","positive","shape","u"],"version":11,"annotation":"magnet","shortcodes":["magnet"]},{"emoji":"๐Ÿชœ","group":7,"order":4407,"tags":["climb","rung","step"],"version":13,"annotation":"ladder","shortcodes":["ladder"]},{"emoji":"๐Ÿช","group":7,"order":4408,"tags":["bury","dig","garden","hole","plant","scoop","snow","spade"],"version":16,"annotation":"shovel","shortcodes":["shovel"]},{"emoji":"โš—๏ธ","group":7,"order":4410,"tags":["chemistry","tool"],"version":1,"annotation":"alembic","shortcodes":["alembic"]},{"emoji":"๐Ÿงช","group":7,"order":4411,"tags":["chemist","chemistry","experiment","lab","science","test","tube"],"version":11,"annotation":"test tube","shortcodes":["test_tube"]},{"emoji":"๐Ÿงซ","group":7,"order":4412,"tags":["bacteria","biologist","biology","culture","dish","lab","petri"],"version":11,"annotation":"petri dish","shortcodes":["petri_dish"]},{"emoji":"๐Ÿงฌ","group":7,"order":4413,"tags":["biologist","evolution","gene","genetics","life"],"version":11,"annotation":"dna","shortcodes":["dna","double_helix"]},{"emoji":"๐Ÿ”ฌ","group":7,"order":4414,"tags":["experiment","lab","science","tool"],"version":1,"annotation":"microscope","shortcodes":["microscope"]},{"emoji":"๐Ÿ”ญ","group":7,"order":4415,"tags":["contact","extraterrestrial","science","tool"],"version":1,"annotation":"telescope","shortcodes":["telescope"]},{"emoji":"๐Ÿ“ก","group":7,"order":4416,"tags":["aliens","antenna","contact","dish","satellite","science"],"version":0.6,"annotation":"satellite antenna","shortcodes":["satellite_antenna"]},{"emoji":"๐Ÿ’‰","group":7,"order":4417,"tags":["doctor","flu","medicine","needle","shot","sick","tool","vaccination"],"version":0.6,"annotation":"syringe","shortcodes":["syringe"]},{"emoji":"๐Ÿฉธ","group":7,"order":4418,"tags":["bleed","blood","donation","drop","injury","medicine","menstruation"],"version":12,"annotation":"drop of blood","shortcodes":["drop_of_blood"]},{"emoji":"๐Ÿ’Š","group":7,"order":4419,"tags":["doctor","drugs","medicated","medicine","pills","sick","vitamin"],"version":0.6,"annotation":"pill","shortcodes":["pill"]},{"emoji":"๐Ÿฉน","group":7,"order":4420,"tags":["adhesive","bandage"],"version":12,"annotation":"adhesive bandage","shortcodes":["adhesive_bandage","bandaid"]},{"emoji":"๐Ÿฉผ","group":7,"order":4421,"tags":["aid","cane","disability","help","hurt","injured","mobility","stick"],"version":14,"annotation":"crutch","shortcodes":["crutch"]},{"emoji":"๐Ÿฉบ","group":7,"order":4422,"tags":["doctor","heart","medicine"],"version":12,"annotation":"stethoscope","shortcodes":["stethoscope"]},{"emoji":"๐Ÿฉป","group":7,"order":4423,"tags":["bones","doctor","medical","skeleton","skull","xray"],"version":14,"annotation":"x-ray","shortcodes":["x-ray","xray"]},{"emoji":"๐Ÿšช","group":7,"order":4424,"tags":["back","closet","front"],"version":0.6,"annotation":"door","shortcodes":["door"]},{"emoji":"๐Ÿ›—","group":7,"order":4425,"tags":["accessibility","hoist","lift"],"version":13,"annotation":"elevator","shortcodes":["elevator"]},{"emoji":"๐Ÿชž","group":7,"order":4426,"tags":["makeup","reflection","reflector","speculum"],"version":13,"annotation":"mirror","shortcodes":["mirror"]},{"emoji":"๐ŸชŸ","group":7,"order":4427,"tags":["air","frame","fresh","opening","transparent","view"],"version":13,"annotation":"window","shortcodes":["window"]},{"emoji":"๐Ÿ›๏ธ","group":7,"order":4429,"tags":["hotel","sleep"],"version":0.7,"annotation":"bed","shortcodes":["bed"]},{"emoji":"๐Ÿ›‹๏ธ","group":7,"order":4431,"tags":["couch","hotel","lamp"],"version":0.7,"annotation":"couch and lamp","shortcodes":["couch_and_lamp"]},{"emoji":"๐Ÿช‘","group":7,"order":4432,"tags":["seat","sit"],"version":12,"annotation":"chair","shortcodes":["chair"]},{"emoji":"๐Ÿšฝ","group":7,"order":4433,"tags":["bathroom"],"version":0.6,"annotation":"toilet","shortcodes":["toilet"]},{"emoji":"๐Ÿช ","group":7,"order":4434,"tags":["cup","force","plumber","poop","suction","toilet"],"version":13,"annotation":"plunger","shortcodes":["plunger"]},{"emoji":"๐Ÿšฟ","group":7,"order":4435,"tags":["water"],"version":1,"annotation":"shower","shortcodes":["shower"]},{"emoji":"๐Ÿ›","group":7,"order":4436,"tags":["bath"],"version":1,"annotation":"bathtub","shortcodes":["bathtub"]},{"emoji":"๐Ÿชค","group":7,"order":4437,"tags":["bait","cheese","lure","mouse","mousetrap","snare","trap"],"version":13,"annotation":"mouse trap","shortcodes":["mouse_trap"]},{"emoji":"๐Ÿช’","group":7,"order":4438,"tags":["sharp","shave"],"version":12,"annotation":"razor","shortcodes":["razor"]},{"emoji":"๐Ÿงด","group":7,"order":4439,"tags":["bottle","lotion","moisturizer","shampoo","sunscreen"],"version":11,"annotation":"lotion bottle","shortcodes":["lotion_bottle"]},{"emoji":"๐Ÿงท","group":7,"order":4440,"tags":["diaper","pin","punk","rock","safety"],"version":11,"annotation":"safety pin","shortcodes":["safety_pin"]},{"emoji":"๐Ÿงน","group":7,"order":4441,"tags":["cleaning","sweeping","witch"],"version":11,"annotation":"broom","shortcodes":["broom"]},{"emoji":"๐Ÿงบ","group":7,"order":4442,"tags":["farming","laundry","picnic"],"version":11,"annotation":"basket","shortcodes":["basket"]},{"emoji":"๐Ÿงป","group":7,"order":4443,"tags":["paper","roll","toilet","towels"],"version":11,"annotation":"roll of paper","shortcodes":["roll_of_paper","toilet_paper"]},{"emoji":"๐Ÿชฃ","group":7,"order":4444,"tags":["cask","pail","vat"],"version":13,"annotation":"bucket","shortcodes":["bucket"]},{"emoji":"๐Ÿงผ","group":7,"order":4445,"tags":["bar","bathing","clean","cleaning","lather","soapdish"],"version":11,"annotation":"soap","shortcodes":["soap"]},{"emoji":"๐Ÿซง","group":7,"order":4446,"tags":["bubble","burp","clean","floating","pearl","soap","underwater"],"version":14,"annotation":"bubbles","shortcodes":["bubbles"]},{"emoji":"๐Ÿชฅ","group":7,"order":4447,"tags":["bathroom","brush","clean","dental","hygiene","teeth","toiletry"],"version":13,"annotation":"toothbrush","shortcodes":["toothbrush"]},{"emoji":"๐Ÿงฝ","group":7,"order":4448,"tags":["absorbing","cleaning","porous","soak"],"version":11,"annotation":"sponge","shortcodes":["sponge"]},{"emoji":"๐Ÿงฏ","group":7,"order":4449,"tags":["extinguish","extinguisher","fire","quench"],"version":11,"annotation":"fire extinguisher","shortcodes":["fire_extinguisher"]},{"emoji":"๐Ÿ›’","group":7,"order":4450,"tags":["cart","shopping","trolley"],"version":3,"annotation":"shopping cart","shortcodes":["shopping_cart"]},{"emoji":"๐Ÿšฌ","group":7,"order":4451,"tags":["smoking"],"version":0.6,"annotation":"cigarette","shortcodes":["cigarette","smoking"]},{"emoji":"โšฐ๏ธ","group":7,"order":4453,"tags":["dead","death","vampire"],"version":1,"annotation":"coffin","shortcodes":["coffin"]},{"emoji":"๐Ÿชฆ","group":7,"order":4454,"tags":["cemetery","dead","grave","graveyard","memorial","rip","tomb","tombstone"],"version":13,"annotation":"headstone","shortcodes":["headstone"]},{"emoji":"โšฑ๏ธ","group":7,"order":4456,"tags":["ashes","death","funeral","urn"],"version":1,"annotation":"funeral urn","shortcodes":["funeral_urn"]},{"emoji":"๐Ÿงฟ","group":7,"order":4457,"tags":["amulet","bead","blue","charm","evil-eye","nazar","talisman"],"version":11,"annotation":"nazar amulet","shortcodes":["nazar_amulet"]},{"emoji":"๐Ÿชฌ","group":7,"order":4458,"tags":["amulet","fatima","fortune","guide","hand","mary","miriam","palm","protect","protection"],"version":14,"annotation":"hamsa","shortcodes":["hamsa"]},{"emoji":"๐Ÿ—ฟ","group":7,"order":4459,"tags":["face","moyai","statue","stoneface","travel"],"version":0.6,"annotation":"moai","shortcodes":["moai","moyai"]},{"emoji":"๐Ÿชง","group":7,"order":4460,"tags":["card","demonstration","notice","picket","plaque","protest","sign"],"version":13,"annotation":"placard","shortcodes":["placard"]},{"emoji":"๐Ÿชช","group":7,"order":4461,"tags":["card","credentials","document","id","identification","license","security"],"version":14,"annotation":"identification card","shortcodes":["id_card"]},{"emoji":"๐Ÿง","group":8,"order":4462,"tags":["atm","automated","bank","cash","money","sign","teller"],"version":0.6,"annotation":"ATM sign","shortcodes":["atm"]},{"emoji":"๐Ÿšฎ","group":8,"order":4463,"tags":["bin","litter","litterbin","sign"],"version":1,"annotation":"litter in bin sign","shortcodes":["litter_bin","put_litter_in_its_place"]},{"emoji":"๐Ÿšฐ","group":8,"order":4464,"tags":["drinking","potable","water"],"version":1,"annotation":"potable water","shortcodes":["potable_water"]},{"emoji":"โ™ฟ๏ธ","group":8,"order":4465,"tags":["access","handicap","symbol","wheelchair"],"version":0.6,"annotation":"wheelchair symbol","shortcodes":["handicapped","wheelchair"]},{"emoji":"๐Ÿšน๏ธ","group":8,"order":4466,"tags":["bathroom","lavatory","man","menโ€™s","restroom","room","toilet","wc"],"version":0.6,"annotation":"menโ€™s room","shortcodes":["mens"]},{"emoji":"๐Ÿšบ๏ธ","group":8,"order":4467,"tags":["bathroom","lavatory","restroom","room","toilet","wc","woman","womenโ€™s"],"version":0.6,"annotation":"womenโ€™s room","shortcodes":["womens"]},{"emoji":"๐Ÿšป","group":8,"order":4468,"tags":["bathroom","lavatory","toilet","wc"],"version":0.6,"annotation":"restroom","shortcodes":["bathroom","restroom"]},{"emoji":"๐Ÿšผ๏ธ","group":8,"order":4469,"tags":["baby","changing","symbol"],"version":0.6,"annotation":"baby symbol","shortcodes":["baby_symbol"]},{"emoji":"๐Ÿšพ","group":8,"order":4470,"tags":["bathroom","closet","lavatory","restroom","toilet","water","wc"],"version":0.6,"annotation":"water closet","shortcodes":["water_closet","wc"]},{"emoji":"๐Ÿ›‚","group":8,"order":4471,"tags":["control","passport"],"version":1,"annotation":"passport control","shortcodes":["passport_control"]},{"emoji":"๐Ÿ›ƒ","group":8,"order":4472,"tags":["packing"],"version":1,"annotation":"customs","shortcodes":["customs"]},{"emoji":"๐Ÿ›„","group":8,"order":4473,"tags":["arrived","baggage","bags","case","checked","claim","journey","packing","plane","ready","travel","trip"],"version":1,"annotation":"baggage claim","shortcodes":["baggage_claim"]},{"emoji":"๐Ÿ›…","group":8,"order":4474,"tags":["baggage","case","left","locker","luggage"],"version":1,"annotation":"left luggage","shortcodes":["left_luggage"]},{"emoji":"โš ๏ธ","group":8,"order":4476,"tags":["caution"],"version":0.6,"annotation":"warning","shortcodes":["warning"]},{"emoji":"๐Ÿšธ","group":8,"order":4477,"tags":["child","children","crossing","pedestrian","traffic"],"version":1,"annotation":"children crossing","shortcodes":["children_crossing"]},{"emoji":"โ›”๏ธ","group":8,"order":4478,"tags":["do","entry","fail","forbidden","no","not","pass","prohibited","traffic"],"version":0.6,"annotation":"no entry","shortcodes":["no_entry"]},{"emoji":"๐Ÿšซ","group":8,"order":4479,"tags":["entry","forbidden","no","not","smoke"],"version":0.6,"annotation":"prohibited","shortcodes":["no_entry_sign"]},{"emoji":"๐Ÿšณ","group":8,"order":4480,"tags":["bicycle","bicycles","bike","forbidden","no","not","prohibited"],"version":1,"annotation":"no bicycles","shortcodes":["no_bicycles"]},{"emoji":"๐Ÿšญ๏ธ","group":8,"order":4481,"tags":["forbidden","no","not","prohibited","smoke","smoking"],"version":0.6,"annotation":"no smoking","shortcodes":["no_smoking"]},{"emoji":"๐Ÿšฏ","group":8,"order":4482,"tags":["forbidden","litter","littering","no","not","prohibited"],"version":1,"annotation":"no littering","shortcodes":["do_not_litter","no_littering"]},{"emoji":"๐Ÿšฑ","group":8,"order":4483,"tags":["dry","non-drinking","non-potable","prohibited","water"],"version":1,"annotation":"non-potable water","shortcodes":["non-potable_water"]},{"emoji":"๐Ÿšท","group":8,"order":4484,"tags":["forbidden","no","not","pedestrian","pedestrians","prohibited"],"version":1,"annotation":"no pedestrians","shortcodes":["no_pedestrians"]},{"emoji":"๐Ÿ“ต","group":8,"order":4485,"tags":["cell","forbidden","mobile","no","not","phone","phones","prohibited","telephone"],"version":1,"annotation":"no mobile phones","shortcodes":["no_mobile_phones"]},{"emoji":"๐Ÿ”ž","group":8,"order":4486,"tags":["18","age","eighteen","forbidden","no","not","one","prohibited","restriction","underage"],"version":0.6,"annotation":"no one under eighteen","shortcodes":["no_one_under_18","underage"]},{"emoji":"โ˜ข๏ธ","group":8,"order":4488,"tags":["sign"],"version":1,"annotation":"radioactive","shortcodes":["radioactive"]},{"emoji":"โ˜ฃ๏ธ","group":8,"order":4490,"tags":["sign"],"version":1,"annotation":"biohazard","shortcodes":["biohazard"]},{"emoji":"โฌ†๏ธ","group":8,"order":4492,"tags":["arrow","cardinal","direction","north","up"],"version":0.6,"annotation":"up arrow","shortcodes":["arrow_up"]},{"emoji":"โ†—๏ธ","group":8,"order":4494,"tags":["arrow","direction","intercardinal","northeast","up-right"],"version":0.6,"annotation":"up-right arrow","shortcodes":["arrow_upper_right"]},{"emoji":"โžก๏ธ","group":8,"order":4496,"tags":["arrow","cardinal","direction","east","right"],"version":0.6,"annotation":"right arrow","shortcodes":["arrow_right"]},{"emoji":"โ†˜๏ธ","group":8,"order":4498,"tags":["arrow","direction","down-right","intercardinal","southeast"],"version":0.6,"annotation":"down-right arrow","shortcodes":["arrow_lower_right"]},{"emoji":"โฌ‡๏ธ","group":8,"order":4500,"tags":["arrow","cardinal","direction","down","south"],"version":0.6,"annotation":"down arrow","shortcodes":["arrow_down"]},{"emoji":"โ†™๏ธ","group":8,"order":4502,"tags":["arrow","direction","down-left","intercardinal","southwest"],"version":0.6,"annotation":"down-left arrow","shortcodes":["arrow_lower_left"]},{"emoji":"โฌ…๏ธ","group":8,"order":4504,"tags":["arrow","cardinal","direction","left","west"],"version":0.6,"annotation":"left arrow","shortcodes":["arrow_left"]},{"emoji":"โ†–๏ธ","group":8,"order":4506,"tags":["arrow","direction","intercardinal","northwest","up-left"],"version":0.6,"annotation":"up-left arrow","shortcodes":["arrow_upper_left"]},{"emoji":"โ†•๏ธ","group":8,"order":4508,"tags":["arrow","up-down"],"version":0.6,"annotation":"up-down arrow","shortcodes":["arrow_up_down"]},{"emoji":"โ†”๏ธ","group":8,"order":4510,"tags":["arrow","left-right"],"version":0.6,"annotation":"left-right arrow","shortcodes":["left_right_arrow"]},{"emoji":"โ†ฉ๏ธ","group":8,"order":4512,"tags":["arrow","curving","left","right"],"version":0.6,"annotation":"right arrow curving left","shortcodes":["arrow_left_hook","leftwards_arrow_with_hook"]},{"emoji":"โ†ช๏ธ","group":8,"order":4514,"tags":["arrow","curving","left","right"],"version":0.6,"annotation":"left arrow curving right","shortcodes":["arrow_right_hook","rightwards_arrow_with_hook"]},{"emoji":"โคด๏ธ","group":8,"order":4516,"tags":["arrow","curving","right","up"],"version":0.6,"annotation":"right arrow curving up","shortcodes":["arrow_heading_up"]},{"emoji":"โคต๏ธ","group":8,"order":4518,"tags":["arrow","curving","down","right"],"version":0.6,"annotation":"right arrow curving down","shortcodes":["arrow_heading_down"]},{"emoji":"๐Ÿ”ƒ","group":8,"order":4519,"tags":["arrow","arrows","clockwise","refresh","reload","vertical"],"version":0.6,"annotation":"clockwise vertical arrows","shortcodes":["arrows_clockwise","clockwise"]},{"emoji":"๐Ÿ”„","group":8,"order":4520,"tags":["again","anticlockwise","arrow","arrows","button","counterclockwise","deja","refresh","rewindershins","vu"],"version":1,"annotation":"counterclockwise arrows button","shortcodes":["arrows_counterclockwise","counterclockwise"]},{"emoji":"๐Ÿ”™","group":8,"order":4521,"tags":["arrow","back"],"version":0.6,"annotation":"BACK arrow","shortcodes":["back"]},{"emoji":"๐Ÿ”š","group":8,"order":4522,"tags":["arrow","end"],"version":0.6,"annotation":"END arrow","shortcodes":["end"]},{"emoji":"๐Ÿ”›","group":8,"order":4523,"tags":["arrow","mark","on!"],"version":0.6,"annotation":"ON! arrow","shortcodes":["on"]},{"emoji":"๐Ÿ”œ","group":8,"order":4524,"tags":["arrow","brb","omw","soon"],"version":0.6,"annotation":"SOON arrow","shortcodes":["soon"]},{"emoji":"๐Ÿ”","group":8,"order":4525,"tags":["arrow","homie","top","up"],"version":0.6,"annotation":"TOP arrow","shortcodes":["top"]},{"emoji":"๐Ÿ›","group":8,"order":4526,"tags":["place","pray","religion","worship"],"version":1,"annotation":"place of worship","shortcodes":["place_of_worship"]},{"emoji":"โš›๏ธ","group":8,"order":4528,"tags":["atheist","atom","symbol"],"version":1,"annotation":"atom symbol","shortcodes":["atom","atom_symbol"]},{"emoji":"๐Ÿ•‰๏ธ","group":8,"order":4530,"tags":["hindu","religion"],"version":0.7,"annotation":"om","shortcodes":["om"]},{"emoji":"โœก๏ธ","group":8,"order":4532,"tags":["david","jew","jewish","judaism","religion","star"],"version":0.7,"annotation":"star of David","shortcodes":["star_of_david"]},{"emoji":"โ˜ธ๏ธ","group":8,"order":4534,"tags":["buddhist","dharma","religion","wheel"],"version":0.7,"annotation":"wheel of dharma","shortcodes":["wheel_of_dharma"]},{"emoji":"โ˜ฏ๏ธ","group":8,"order":4536,"tags":["difficult","lives","religion","tao","taoist","total","yang","yin","yinyang"],"version":0.7,"annotation":"yin yang","shortcodes":["yin_yang"]},{"emoji":"โœ๏ธ","group":8,"order":4538,"tags":["christ","christian","cross","latin","religion"],"version":0.7,"annotation":"latin cross","shortcodes":["latin_cross"]},{"emoji":"โ˜ฆ๏ธ","group":8,"order":4540,"tags":["christian","cross","orthodox","religion"],"version":1,"annotation":"orthodox cross","shortcodes":["orthodox_cross"]},{"emoji":"โ˜ช๏ธ","group":8,"order":4542,"tags":["crescent","islam","muslim","ramadan","religion","star"],"version":0.7,"annotation":"star and crescent","shortcodes":["star_and_crescent"]},{"emoji":"โ˜ฎ๏ธ","group":8,"order":4544,"tags":["healing","peace","peaceful","symbol"],"version":1,"annotation":"peace symbol","shortcodes":["peace","peace_symbol"]},{"emoji":"๐Ÿ•Ž","group":8,"order":4545,"tags":["candelabrum","candlestick","hanukkah","jewish","judaism","religion"],"version":1,"annotation":"menorah","shortcodes":["menorah"]},{"emoji":"๐Ÿ”ฏ","group":8,"order":4546,"tags":["dotted","fortune","jewish","judaism","six-pointed","star"],"version":0.6,"annotation":"dotted six-pointed star","shortcodes":["six_pointed_star"]},{"emoji":"๐Ÿชฏ","group":8,"order":4547,"tags":["deg","fateh","khalsa","religion","sikh","sikhism","tegh"],"version":15,"annotation":"khanda","shortcodes":["khanda"]},{"emoji":"โ™ˆ๏ธ","group":8,"order":4548,"tags":["aries","horoscope","ram","zodiac"],"version":0.6,"annotation":"Aries","shortcodes":["aries"]},{"emoji":"โ™‰๏ธ","group":8,"order":4549,"tags":["bull","horoscope","ox","taurus","zodiac"],"version":0.6,"annotation":"Taurus","shortcodes":["taurus"]},{"emoji":"โ™Š๏ธ","group":8,"order":4550,"tags":["gemini","horoscope","twins","zodiac"],"version":0.6,"annotation":"Gemini","shortcodes":["gemini"]},{"emoji":"โ™‹๏ธ","group":8,"order":4551,"tags":["cancer","crab","horoscope","zodiac"],"version":0.6,"annotation":"Cancer","shortcodes":["cancer"]},{"emoji":"โ™Œ๏ธ","group":8,"order":4552,"tags":["horoscope","leo","lion","zodiac"],"version":0.6,"annotation":"Leo","shortcodes":["leo"]},{"emoji":"โ™๏ธ","group":8,"order":4553,"tags":["horoscope","virgo","zodiac"],"version":0.6,"annotation":"Virgo","shortcodes":["virgo"]},{"emoji":"โ™Ž๏ธ","group":8,"order":4554,"tags":["balance","horoscope","justice","libra","scales","zodiac"],"version":0.6,"annotation":"Libra","shortcodes":["libra"]},{"emoji":"โ™๏ธ","group":8,"order":4555,"tags":["horoscope","scorpio","scorpion","scorpius","zodiac"],"version":0.6,"annotation":"Scorpio","shortcodes":["scorpius"]},{"emoji":"โ™๏ธ","group":8,"order":4556,"tags":["archer","horoscope","sagittarius","zodiac"],"version":0.6,"annotation":"Sagittarius","shortcodes":["sagittarius"]},{"emoji":"โ™‘๏ธ","group":8,"order":4557,"tags":["capricorn","goat","horoscope","zodiac"],"version":0.6,"annotation":"Capricorn","shortcodes":["capricorn"]},{"emoji":"โ™’๏ธ","group":8,"order":4558,"tags":["aquarius","bearer","horoscope","water","zodiac"],"version":0.6,"annotation":"Aquarius","shortcodes":["aquarius"]},{"emoji":"โ™“๏ธ","group":8,"order":4559,"tags":["fish","horoscope","pisces","zodiac"],"version":0.6,"annotation":"Pisces","shortcodes":["pisces"]},{"emoji":"โ›Ž๏ธ","group":8,"order":4560,"tags":["bearer","ophiuchus","serpent","snake","zodiac"],"version":0.6,"annotation":"Ophiuchus","shortcodes":["ophiuchus"]},{"emoji":"๐Ÿ”€","group":8,"order":4561,"tags":["arrow","button","crossed","shuffle","tracks"],"version":1,"annotation":"shuffle tracks button","shortcodes":["shuffle","twisted_rightwards_arrows"]},{"emoji":"๐Ÿ”","group":8,"order":4562,"tags":["arrow","button","clockwise","repeat"],"version":1,"annotation":"repeat button","shortcodes":["repeat"]},{"emoji":"๐Ÿ”‚","group":8,"order":4563,"tags":["arrow","button","clockwise","once","repeat","single"],"version":1,"annotation":"repeat single button","shortcodes":["repeat_one"]},{"emoji":"โ–ถ๏ธ","group":8,"order":4565,"tags":["arrow","button","play","right","triangle"],"version":0.6,"annotation":"play button","shortcodes":["arrow_forward","play"]},{"emoji":"โฉ๏ธ","group":8,"order":4566,"tags":["arrow","button","double","fast","fast-forward","forward"],"version":0.6,"annotation":"fast-forward button","shortcodes":["fast_forward"]},{"emoji":"โญ๏ธ","group":8,"order":4568,"tags":["arrow","button","next","scene","track","triangle"],"version":0.7,"annotation":"next track button","shortcodes":["next_track"]},{"emoji":"โฏ๏ธ","group":8,"order":4570,"tags":["arrow","button","pause","play","right","triangle"],"version":1,"annotation":"play or pause button","shortcodes":["play_pause"]},{"emoji":"โ—€๏ธ","group":8,"order":4572,"tags":["arrow","button","left","reverse","triangle"],"version":0.6,"annotation":"reverse button","shortcodes":["arrow_backward","reverse"]},{"emoji":"โช๏ธ","group":8,"order":4573,"tags":["arrow","button","double","fast","reverse","rewind"],"version":0.6,"annotation":"fast reverse button","shortcodes":["fast_reverse","rewind"]},{"emoji":"โฎ๏ธ","group":8,"order":4575,"tags":["arrow","button","last","previous","scene","track","triangle"],"version":0.7,"annotation":"last track button","shortcodes":["previous_track"]},{"emoji":"๐Ÿ”ผ","group":8,"order":4576,"tags":["arrow","button","red","up","upwards"],"version":0.6,"annotation":"upwards button","shortcodes":["arrow_up_small","up"]},{"emoji":"โซ๏ธ","group":8,"order":4577,"tags":["arrow","button","double","fast","up"],"version":0.6,"annotation":"fast up button","shortcodes":["arrow_double_up","fast_up"]},{"emoji":"๐Ÿ”ฝ","group":8,"order":4578,"tags":["arrow","button","down","downwards","red"],"version":0.6,"annotation":"downwards button","shortcodes":["arrow_down_small","down"]},{"emoji":"โฌ๏ธ","group":8,"order":4579,"tags":["arrow","button","double","down","fast"],"version":0.6,"annotation":"fast down button","shortcodes":["arrow_double_down","fast_down"]},{"emoji":"โธ๏ธ","group":8,"order":4581,"tags":["bar","button","double","pause","vertical"],"version":0.7,"annotation":"pause button","shortcodes":["pause"]},{"emoji":"โน๏ธ","group":8,"order":4583,"tags":["button","square","stop"],"version":0.7,"annotation":"stop button","shortcodes":["stop"]},{"emoji":"โบ๏ธ","group":8,"order":4585,"tags":["button","circle","record"],"version":0.7,"annotation":"record button","shortcodes":["record"]},{"emoji":"โ๏ธ","group":8,"order":4587,"tags":["button","eject"],"version":1,"annotation":"eject button","shortcodes":["eject"]},{"emoji":"๐ŸŽฆ","group":8,"order":4588,"tags":["camera","film","movie"],"version":0.6,"annotation":"cinema","shortcodes":["cinema"]},{"emoji":"๐Ÿ”…","group":8,"order":4589,"tags":["brightness","button","dim","low"],"version":1,"annotation":"dim button","shortcodes":["dim_button","low_brightness"]},{"emoji":"๐Ÿ”†","group":8,"order":4590,"tags":["bright","brightness","button","light"],"version":1,"annotation":"bright button","shortcodes":["bright_button","high_brightness"]},{"emoji":"๐Ÿ“ถ","group":8,"order":4591,"tags":["antenna","bar","bars","cell","communication","mobile","phone","signal","telephone"],"version":0.6,"annotation":"antenna bars","shortcodes":["antenna_bars","signal_strength"]},{"emoji":"๐Ÿ›œ","group":8,"order":4592,"tags":["broadband","computer","connectivity","hotspot","internet","network","router","smartphone","wi-fi","wifi","wlan"],"version":15,"annotation":"wireless","shortcodes":["wireless"]},{"emoji":"๐Ÿ“ณ","group":8,"order":4593,"tags":["cell","communication","mobile","mode","phone","telephone","vibration"],"version":0.6,"annotation":"vibration mode","shortcodes":["vibration_mode"]},{"emoji":"๐Ÿ“ด","group":8,"order":4594,"tags":["cell","mobile","off","phone","telephone"],"version":0.6,"annotation":"mobile phone off","shortcodes":["mobile_phone_off"]},{"emoji":"โ™€๏ธ","group":8,"order":4596,"tags":["female","sign","woman"],"version":4,"annotation":"female sign","shortcodes":["female","female_sign"]},{"emoji":"โ™‚๏ธ","group":8,"order":4598,"tags":["male","man","sign"],"version":4,"annotation":"male sign","shortcodes":["male","male_sign"]},{"emoji":"โšง๏ธ","group":8,"order":4600,"tags":["symbol","transgender"],"version":13,"annotation":"transgender symbol","shortcodes":["transgender_symbol"]},{"emoji":"โœ–๏ธ","group":8,"order":4602,"tags":["cancel","multiplication","sign","x","ร—"],"version":0.6,"annotation":"multiply","shortcodes":["multiplication","multiply"]},{"emoji":"โž•๏ธ","group":8,"order":4603,"tags":["+"],"version":0.6,"annotation":"plus","shortcodes":["plus"]},{"emoji":"โž–๏ธ","group":8,"order":4604,"tags":["-","heavy","math","sign","โˆ’"],"version":0.6,"annotation":"minus","shortcodes":["minus"]},{"emoji":"โž—๏ธ","group":8,"order":4605,"tags":["division","heavy","math","sign","รท"],"version":0.6,"annotation":"divide","shortcodes":["divide","division"]},{"emoji":"๐ŸŸฐ","group":8,"order":4606,"tags":["answer","equal","equality","equals","heavy","math","sign"],"version":14,"annotation":"heavy equals sign","shortcodes":["heavy_equals_sign"]},{"emoji":"โ™พ๏ธ","group":8,"order":4608,"tags":["forever","unbounded","universal"],"version":11,"annotation":"infinity","shortcodes":["infinity"]},{"emoji":"โ€ผ๏ธ","group":8,"order":4610,"tags":["!","!!","bangbang","double","exclamation","mark","punctuation"],"version":0.6,"annotation":"double exclamation mark","shortcodes":["bangbang","double_exclamation"]},{"emoji":"โ‰๏ธ","group":8,"order":4612,"tags":["!","!?","?","exclamation","interrobang","mark","punctuation","question"],"version":0.6,"annotation":"exclamation question mark","shortcodes":["exclamation_question","interrobang"]},{"emoji":"โ“๏ธ","group":8,"order":4613,"tags":["?","mark","punctuation","question","red"],"version":0.6,"annotation":"red question mark","shortcodes":["question"]},{"emoji":"โ”๏ธ","group":8,"order":4614,"tags":["?","mark","outlined","punctuation","question","white"],"version":0.6,"annotation":"white question mark","shortcodes":["white_question"]},{"emoji":"โ•๏ธ","group":8,"order":4615,"tags":["!","exclamation","mark","outlined","punctuation","white"],"version":0.6,"annotation":"white exclamation mark","shortcodes":["white_exclamation"]},{"emoji":"โ—๏ธ","group":8,"order":4616,"tags":["!","exclamation","mark","punctuation","red"],"version":0.6,"annotation":"red exclamation mark","shortcodes":["exclamation"]},{"emoji":"ใ€ฐ๏ธ","group":8,"order":4618,"tags":["dash","punctuation","wavy"],"version":0.6,"annotation":"wavy dash","shortcodes":["wavy_dash"]},{"emoji":"๐Ÿ’ฑ","group":8,"order":4619,"tags":["bank","currency","exchange","money"],"version":0.6,"annotation":"currency exchange","shortcodes":["currency_exchange"]},{"emoji":"๐Ÿ’ฒ","group":8,"order":4620,"tags":["billion","cash","charge","currency","dollar","heavy","million","money","pay","sign"],"version":0.6,"annotation":"heavy dollar sign","shortcodes":["heavy_dollar_sign"]},{"emoji":"โš•๏ธ","group":8,"order":4622,"tags":["aesculapius","medical","medicine","staff","symbol"],"version":4,"annotation":"medical symbol","shortcodes":["medical","medical_symbol"]},{"emoji":"โ™ป๏ธ","group":8,"order":4624,"tags":["recycle","recycling","symbol"],"version":0.6,"annotation":"recycling symbol","shortcodes":["recycle","recycling_symbol"]},{"emoji":"โšœ๏ธ","group":8,"order":4626,"tags":["knights"],"version":1,"annotation":"fleur-de-lis","shortcodes":["fleur-de-lis"]},{"emoji":"๐Ÿ”ฑ","group":8,"order":4627,"tags":["anchor","emblem","poseidon","ship","tool","trident"],"version":0.6,"annotation":"trident emblem","shortcodes":["trident"]},{"emoji":"๐Ÿ“›","group":8,"order":4628,"tags":["badge","name"],"version":0.6,"annotation":"name badge","shortcodes":["name_badge"]},{"emoji":"๐Ÿ”ฐ","group":8,"order":4629,"tags":["beginner","chevron","green","japanese","leaf","symbol","tool","yellow"],"version":0.6,"annotation":"Japanese symbol for beginner","shortcodes":["beginner"]},{"emoji":"โญ•๏ธ","group":8,"order":4630,"tags":["circle","heavy","hollow","large","o","red"],"version":0.6,"annotation":"hollow red circle","shortcodes":["hollow_red_circle","red_o"]},{"emoji":"โœ…๏ธ","group":8,"order":4631,"tags":["button","check","checked","checkmark","complete","completed","done","fixed","mark","tick","โœ“"],"version":0.6,"annotation":"check mark button","shortcodes":["check_mark_button","white_check_mark"]},{"emoji":"โ˜‘๏ธ","group":8,"order":4633,"tags":["ballot","box","check","checked","done","off","tick","โœ“"],"version":0.6,"annotation":"check box with check","shortcodes":["ballot_box_with_check"]},{"emoji":"โœ”๏ธ","group":8,"order":4635,"tags":["check","checked","checkmark","done","heavy","mark","tick","โœ“"],"version":0.6,"annotation":"check mark","shortcodes":["check_mark","heavy_check_mark"]},{"emoji":"โŒ๏ธ","group":8,"order":4636,"tags":["cancel","cross","mark","multiplication","multiply","x","ร—"],"version":0.6,"annotation":"cross mark","shortcodes":["cross_mark","x"]},{"emoji":"โŽ๏ธ","group":8,"order":4637,"tags":["button","cross","mark","multiplication","multiply","square","x","ร—"],"version":0.6,"annotation":"cross mark button","shortcodes":["cross_mark_button","negative_squared_cross_mark"]},{"emoji":"โžฐ๏ธ","group":8,"order":4638,"tags":["curl","curly","loop"],"version":0.6,"annotation":"curly loop","shortcodes":["curly_loop"]},{"emoji":"โžฟ๏ธ","group":8,"order":4639,"tags":["curl","curly","double","loop"],"version":1,"annotation":"double curly loop","shortcodes":["double_curly_loop","loop"]},{"emoji":"ใ€ฝ๏ธ","group":8,"order":4641,"tags":["alternation","mark","part"],"version":0.6,"annotation":"part alternation mark","shortcodes":["part_alternation_mark"]},{"emoji":"โœณ๏ธ","group":8,"order":4643,"tags":["*","asterisk","eight-spoked"],"version":0.6,"annotation":"eight-spoked asterisk","shortcodes":["eight_spoked_asterisk"]},{"emoji":"โœด๏ธ","group":8,"order":4645,"tags":["*","eight-pointed","star"],"version":0.6,"annotation":"eight-pointed star","shortcodes":["eight_pointed_black_star"]},{"emoji":"โ‡๏ธ","group":8,"order":4647,"tags":["*"],"version":0.6,"annotation":"sparkle","shortcodes":["sparkle"]},{"emoji":"ยฉ๏ธ","group":8,"order":4649,"tags":["c"],"version":0.6,"annotation":"copyright","shortcodes":["copyright"]},{"emoji":"ยฎ๏ธ","group":8,"order":4651,"tags":["r"],"version":0.6,"annotation":"registered","shortcodes":["registered"]},{"emoji":"โ„ข๏ธ","group":8,"order":4653,"tags":["mark","tm","trade","trademark"],"version":0.6,"annotation":"trade mark","shortcodes":["tm","trade_mark"]},{"emoji":"๐ŸซŸ","group":8,"order":4654,"tags":["drip","holi","ink","liquid","mess","paint","spill","stain"],"version":16,"annotation":"splatter","shortcodes":["splatter"]},{"emoji":"#๏ธโƒฃ","group":8,"order":4655,"tags":["keycap"],"version":0.6,"annotation":"keycap: #","shortcodes":["hash","number_sign"]},{"emoji":"*๏ธโƒฃ","group":8,"order":4657,"tags":["keycap"],"version":2,"annotation":"keycap: *","shortcodes":["asterisk"]},{"emoji":"0๏ธโƒฃ","group":8,"order":4659,"tags":["keycap"],"version":0.6,"annotation":"keycap: 0","shortcodes":["zero"]},{"emoji":"1๏ธโƒฃ","group":8,"order":4661,"tags":["keycap"],"version":0.6,"annotation":"keycap: 1","shortcodes":["one"]},{"emoji":"2๏ธโƒฃ","group":8,"order":4663,"tags":["keycap"],"version":0.6,"annotation":"keycap: 2","shortcodes":["two"]},{"emoji":"3๏ธโƒฃ","group":8,"order":4665,"tags":["keycap"],"version":0.6,"annotation":"keycap: 3","shortcodes":["three"]},{"emoji":"4๏ธโƒฃ","group":8,"order":4667,"tags":["keycap"],"version":0.6,"annotation":"keycap: 4","shortcodes":["four"]},{"emoji":"5๏ธโƒฃ","group":8,"order":4669,"tags":["keycap"],"version":0.6,"annotation":"keycap: 5","shortcodes":["five"]},{"emoji":"6๏ธโƒฃ","group":8,"order":4671,"tags":["keycap"],"version":0.6,"annotation":"keycap: 6","shortcodes":["six"]},{"emoji":"7๏ธโƒฃ","group":8,"order":4673,"tags":["keycap"],"version":0.6,"annotation":"keycap: 7","shortcodes":["seven"]},{"emoji":"8๏ธโƒฃ","group":8,"order":4675,"tags":["keycap"],"version":0.6,"annotation":"keycap: 8","shortcodes":["eight"]},{"emoji":"9๏ธโƒฃ","group":8,"order":4677,"tags":["keycap"],"version":0.6,"annotation":"keycap: 9","shortcodes":["nine"]},{"emoji":"๐Ÿ”Ÿ","group":8,"order":4679,"tags":["keycap"],"version":0.6,"annotation":"keycap: 10","shortcodes":["ten"]},{"emoji":"๐Ÿ” ","group":8,"order":4680,"tags":["abcd","input","latin","letters","uppercase"],"version":0.6,"annotation":"input latin uppercase","shortcodes":["capital_abcd"]},{"emoji":"๐Ÿ”ก","group":8,"order":4681,"tags":["abcd","input","latin","letters","lowercase"],"version":0.6,"annotation":"input latin lowercase","shortcodes":["abcd"]},{"emoji":"๐Ÿ”ข","group":8,"order":4682,"tags":["1234","input","numbers"],"version":0.6,"annotation":"input numbers","shortcodes":["1234"]},{"emoji":"๐Ÿ”ฃ","group":8,"order":4683,"tags":["%","&","input","symbols","โ™ช","ใ€’"],"version":0.6,"annotation":"input symbols","shortcodes":["symbols"]},{"emoji":"๐Ÿ”ค","group":8,"order":4684,"tags":["abc","alphabet","input","latin","letters"],"version":0.6,"annotation":"input latin letters","shortcodes":["abc"]},{"emoji":"๐Ÿ…ฐ๏ธ","group":8,"order":4686,"tags":["blood","button","type"],"version":0.6,"annotation":"A button (blood type)","shortcodes":["a","a_blood"]},{"emoji":"๐Ÿ†Ž","group":8,"order":4687,"tags":["ab","blood","button","type"],"version":0.6,"annotation":"AB button (blood type)","shortcodes":["ab","ab_blood"]},{"emoji":"๐Ÿ…ฑ๏ธ","group":8,"order":4689,"tags":["b","blood","button","type"],"version":0.6,"annotation":"B button (blood type)","shortcodes":["b","b_blood"]},{"emoji":"๐Ÿ†‘","group":8,"order":4690,"tags":["button","cl"],"version":0.6,"annotation":"CL button","shortcodes":["cl"]},{"emoji":"๐Ÿ†’","group":8,"order":4691,"tags":["button","cool"],"version":0.6,"annotation":"COOL button","shortcodes":["cool"]},{"emoji":"๐Ÿ†“","group":8,"order":4692,"tags":["button","free"],"version":0.6,"annotation":"FREE button","shortcodes":["free"]},{"emoji":"โ„น๏ธ","group":8,"order":4694,"tags":["i"],"version":0.6,"annotation":"information","shortcodes":["info","information_source"]},{"emoji":"๐Ÿ†”","group":8,"order":4695,"tags":["button","id","identity"],"version":0.6,"annotation":"ID button","shortcodes":["id"]},{"emoji":"โ“‚๏ธ","group":8,"order":4697,"tags":["circle","circled","m"],"version":0.6,"annotation":"circled M","shortcodes":["m"]},{"emoji":"๐Ÿ†•","group":8,"order":4698,"tags":["button","new"],"version":0.6,"annotation":"NEW button","shortcodes":["new"]},{"emoji":"๐Ÿ†–","group":8,"order":4699,"tags":["button","ng"],"version":0.6,"annotation":"NG button","shortcodes":["ng"]},{"emoji":"๐Ÿ…พ๏ธ","group":8,"order":4701,"tags":["blood","button","o","type"],"version":0.6,"annotation":"O button (blood type)","shortcodes":["o","o_blood"]},{"emoji":"๐Ÿ†—","group":8,"order":4702,"tags":["button","ok","okay"],"version":0.6,"annotation":"OK button","shortcodes":["ok"]},{"emoji":"๐Ÿ…ฟ๏ธ","group":8,"order":4704,"tags":["button","p","parking"],"version":0.6,"annotation":"P button","shortcodes":["parking"]},{"emoji":"๐Ÿ†˜","group":8,"order":4705,"tags":["button","help","sos"],"version":0.6,"annotation":"SOS button","shortcodes":["sos"]},{"emoji":"๐Ÿ†™","group":8,"order":4706,"tags":["button","mark","up","up!"],"version":0.6,"annotation":"UP! button","shortcodes":["up2"]},{"emoji":"๐Ÿ†š","group":8,"order":4707,"tags":["button","versus","vs"],"version":0.6,"annotation":"VS button","shortcodes":["vs"]},{"emoji":"๐Ÿˆ","group":8,"order":4708,"tags":["button","here","japanese","katakana"],"version":0.6,"annotation":"Japanese โ€œhereโ€ button","shortcodes":["ja_here","koko"]},{"emoji":"๐Ÿˆ‚๏ธ","group":8,"order":4710,"tags":["button","charge","japanese","katakana","service"],"version":0.6,"annotation":"Japanese โ€œservice chargeโ€ button","shortcodes":["ja_service_charge"]},{"emoji":"๐Ÿˆท๏ธ","group":8,"order":4712,"tags":["amount","button","ideograph","japanese","monthly"],"version":0.6,"annotation":"Japanese โ€œmonthly amountโ€ button","shortcodes":["ja_monthly_amount"]},{"emoji":"๐Ÿˆถ","group":8,"order":4713,"tags":["button","charge","free","ideograph","japanese","not"],"version":0.6,"annotation":"Japanese โ€œnot free of chargeโ€ button","shortcodes":["ja_not_free_of_carge"]},{"emoji":"๐Ÿˆฏ๏ธ","group":8,"order":4714,"tags":["button","ideograph","japanese","reserved"],"version":0.6,"annotation":"Japanese โ€œreservedโ€ button","shortcodes":["ja_reserved"]},{"emoji":"๐Ÿ‰","group":8,"order":4715,"tags":["bargain","button","ideograph","japanese"],"version":0.6,"annotation":"Japanese โ€œbargainโ€ button","shortcodes":["ideograph_advantage","ja_bargain"]},{"emoji":"๐Ÿˆน","group":8,"order":4716,"tags":["button","discount","ideograph","japanese"],"version":0.6,"annotation":"Japanese โ€œdiscountโ€ button","shortcodes":["ja_discount"]},{"emoji":"๐Ÿˆš๏ธ","group":8,"order":4717,"tags":["button","charge","free","ideograph","japanese"],"version":0.6,"annotation":"Japanese โ€œfree of chargeโ€ button","shortcodes":["ja_free_of_charge"]},{"emoji":"๐Ÿˆฒ","group":8,"order":4718,"tags":["button","ideograph","japanese","prohibited"],"version":0.6,"annotation":"Japanese โ€œprohibitedโ€ button","shortcodes":["ja_prohibited"]},{"emoji":"๐Ÿ‰‘","group":8,"order":4719,"tags":["acceptable","button","ideograph","japanese"],"version":0.6,"annotation":"Japanese โ€œacceptableโ€ button","shortcodes":["accept","ja_acceptable"]},{"emoji":"๐Ÿˆธ","group":8,"order":4720,"tags":["application","button","ideograph","japanese"],"version":0.6,"annotation":"Japanese โ€œapplicationโ€ button","shortcodes":["ja_application"]},{"emoji":"๐Ÿˆด","group":8,"order":4721,"tags":["button","grade","ideograph","japanese","passing"],"version":0.6,"annotation":"Japanese โ€œpassing gradeโ€ button","shortcodes":["ja_passing_grade"]},{"emoji":"๐Ÿˆณ","group":8,"order":4722,"tags":["button","ideograph","japanese","vacancy"],"version":0.6,"annotation":"Japanese โ€œvacancyโ€ button","shortcodes":["ja_vacancy"]},{"emoji":"ใŠ—๏ธ","group":8,"order":4724,"tags":["button","congratulations","ideograph","japanese"],"version":0.6,"annotation":"Japanese โ€œcongratulationsโ€ button","shortcodes":["congratulations","ja_congratulations"]},{"emoji":"ใŠ™๏ธ","group":8,"order":4726,"tags":["button","ideograph","japanese","secret"],"version":0.6,"annotation":"Japanese โ€œsecretโ€ button","shortcodes":["ja_secret","secret"]},{"emoji":"๐Ÿˆบ","group":8,"order":4727,"tags":["business","button","ideograph","japanese","open"],"version":0.6,"annotation":"Japanese โ€œopen for businessโ€ button","shortcodes":["ja_open_for_business"]},{"emoji":"๐Ÿˆต","group":8,"order":4728,"tags":["button","ideograph","japanese","no","vacancy"],"version":0.6,"annotation":"Japanese โ€œno vacancyโ€ button","shortcodes":["ja_no_vacancy"]},{"emoji":"๐Ÿ”ด","group":8,"order":4729,"tags":["circle","geometric","red"],"version":0.6,"annotation":"red circle","shortcodes":["red_circle"]},{"emoji":"๐ŸŸ ","group":8,"order":4730,"tags":["circle","orange"],"version":12,"annotation":"orange circle","shortcodes":["orange_circle"]},{"emoji":"๐ŸŸก","group":8,"order":4731,"tags":["circle","yellow"],"version":12,"annotation":"yellow circle","shortcodes":["yellow_circle"]},{"emoji":"๐ŸŸข","group":8,"order":4732,"tags":["circle","green"],"version":12,"annotation":"green circle","shortcodes":["green_circle"]},{"emoji":"๐Ÿ”ต","group":8,"order":4733,"tags":["blue","circle","geometric"],"version":0.6,"annotation":"blue circle","shortcodes":["blue_circle"]},{"emoji":"๐ŸŸฃ","group":8,"order":4734,"tags":["circle","purple"],"version":12,"annotation":"purple circle","shortcodes":["purple_circle"]},{"emoji":"๐ŸŸค","group":8,"order":4735,"tags":["brown","circle"],"version":12,"annotation":"brown circle","shortcodes":["brown_circle"]},{"emoji":"โšซ๏ธ","group":8,"order":4736,"tags":["black","circle","geometric"],"version":0.6,"annotation":"black circle","shortcodes":["black_circle"]},{"emoji":"โšช๏ธ","group":8,"order":4737,"tags":["circle","geometric","white"],"version":0.6,"annotation":"white circle","shortcodes":["white_circle"]},{"emoji":"๐ŸŸฅ","group":8,"order":4738,"tags":["card","penalty","red","square"],"version":12,"annotation":"red square","shortcodes":["red_square"]},{"emoji":"๐ŸŸง","group":8,"order":4739,"tags":["orange","square"],"version":12,"annotation":"orange square","shortcodes":["orange_square"]},{"emoji":"๐ŸŸจ","group":8,"order":4740,"tags":["card","penalty","square","yellow"],"version":12,"annotation":"yellow square","shortcodes":["yellow_square"]},{"emoji":"๐ŸŸฉ","group":8,"order":4741,"tags":["green","square"],"version":12,"annotation":"green square","shortcodes":["green_square"]},{"emoji":"๐ŸŸฆ","group":8,"order":4742,"tags":["blue","square"],"version":12,"annotation":"blue square","shortcodes":["blue_square"]},{"emoji":"๐ŸŸช","group":8,"order":4743,"tags":["purple","square"],"version":12,"annotation":"purple square","shortcodes":["purple_square"]},{"emoji":"๐ŸŸซ","group":8,"order":4744,"tags":["brown","square"],"version":12,"annotation":"brown square","shortcodes":["brown_square"]},{"emoji":"โฌ›๏ธ","group":8,"order":4745,"tags":["black","geometric","large","square"],"version":0.6,"annotation":"black large square","shortcodes":["black_large_square"]},{"emoji":"โฌœ๏ธ","group":8,"order":4746,"tags":["geometric","large","square","white"],"version":0.6,"annotation":"white large square","shortcodes":["white_large_square"]},{"emoji":"โ—ผ๏ธ","group":8,"order":4748,"tags":["black","geometric","medium","square"],"version":0.6,"annotation":"black medium square","shortcodes":["black_medium_square"]},{"emoji":"โ—ป๏ธ","group":8,"order":4750,"tags":["geometric","medium","square","white"],"version":0.6,"annotation":"white medium square","shortcodes":["white_medium_square"]},{"emoji":"โ—พ๏ธ","group":8,"order":4751,"tags":["black","geometric","medium-small","square"],"version":0.6,"annotation":"black medium-small square","shortcodes":["black_medium_small_square"]},{"emoji":"โ—ฝ๏ธ","group":8,"order":4752,"tags":["geometric","medium-small","square","white"],"version":0.6,"annotation":"white medium-small square","shortcodes":["white_medium_small_square"]},{"emoji":"โ–ช๏ธ","group":8,"order":4754,"tags":["black","geometric","small","square"],"version":0.6,"annotation":"black small square","shortcodes":["black_small_square"]},{"emoji":"โ–ซ๏ธ","group":8,"order":4756,"tags":["geometric","small","square","white"],"version":0.6,"annotation":"white small square","shortcodes":["white_small_square"]},{"emoji":"๐Ÿ”ถ","group":8,"order":4757,"tags":["diamond","geometric","large","orange"],"version":0.6,"annotation":"large orange diamond","shortcodes":["large_orange_diamond"]},{"emoji":"๐Ÿ”ท","group":8,"order":4758,"tags":["blue","diamond","geometric","large"],"version":0.6,"annotation":"large blue diamond","shortcodes":["large_blue_diamond"]},{"emoji":"๐Ÿ”ธ","group":8,"order":4759,"tags":["diamond","geometric","orange","small"],"version":0.6,"annotation":"small orange diamond","shortcodes":["small_orange_diamond"]},{"emoji":"๐Ÿ”น","group":8,"order":4760,"tags":["blue","diamond","geometric","small"],"version":0.6,"annotation":"small blue diamond","shortcodes":["small_blue_diamond"]},{"emoji":"๐Ÿ”บ","group":8,"order":4761,"tags":["geometric","pointed","red","triangle","up"],"version":0.6,"annotation":"red triangle pointed up","shortcodes":["small_red_triangle"]},{"emoji":"๐Ÿ”ป","group":8,"order":4762,"tags":["down","geometric","pointed","red","triangle"],"version":0.6,"annotation":"red triangle pointed down","shortcodes":["small_red_triangle_down"]},{"emoji":"๐Ÿ’ ","group":8,"order":4763,"tags":["comic","diamond","dot","geometric"],"version":0.6,"annotation":"diamond with a dot","shortcodes":["diamond_shape_with_a_dot_inside","diamond_with_a_dot"]},{"emoji":"๐Ÿ”˜","group":8,"order":4764,"tags":["button","geometric","radio"],"version":0.6,"annotation":"radio button","shortcodes":["radio_button"]},{"emoji":"๐Ÿ”ณ","group":8,"order":4765,"tags":["button","geometric","outlined","square","white"],"version":0.6,"annotation":"white square button","shortcodes":["white_square_button"]},{"emoji":"๐Ÿ”ฒ","group":8,"order":4766,"tags":["black","button","geometric","square"],"version":0.6,"annotation":"black square button","shortcodes":["black_square_button"]},{"emoji":"๐Ÿ","group":9,"order":4767,"tags":["checkered","chequered","finish","flag","flags","game","race","racing","sport","win"],"version":0.6,"annotation":"chequered flag","shortcodes":["checkered_flag"]},{"emoji":"๐Ÿšฉ","group":9,"order":4768,"tags":["construction","flag","golf","post","triangular"],"version":0.6,"annotation":"triangular flag","shortcodes":["triangular_flag","triangular_flag_on_post"]},{"emoji":"๐ŸŽŒ","group":9,"order":4769,"tags":["celebration","cross","crossed","flags","japanese"],"version":0.6,"annotation":"crossed flags","shortcodes":["crossed_flags"]},{"emoji":"๐Ÿด","group":9,"order":4770,"tags":["black","flag","waving"],"version":1,"annotation":"black flag","shortcodes":["black_flag"]},{"emoji":"๐Ÿณ๏ธ","group":9,"order":4772,"tags":["flag","waving","white"],"version":0.7,"annotation":"white flag","shortcodes":["white_flag"]},{"emoji":"๐Ÿณ๏ธโ€๐ŸŒˆ","group":9,"order":4773,"tags":["bisexual","flag","gay","genderqueer","glbt","glbtq","lesbian","lgbt","lgbtq","lgbtqia","pride","queer","rainbow","trans","transgender"],"version":4,"annotation":"rainbow flag","shortcodes":["rainbow_flag"]},{"emoji":"๐Ÿณ๏ธโ€โšง๏ธ","group":9,"order":4775,"tags":["blue","flag","light","pink","transgender","white"],"version":13,"annotation":"transgender flag","shortcodes":["transgender_flag"]},{"emoji":"๐Ÿดโ€โ˜ ๏ธ","group":9,"order":4779,"tags":["flag","jolly","pirate","plunder","roger","treasure"],"version":11,"annotation":"pirate flag","shortcodes":["jolly_roger","pirate_flag"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡จ","group":9,"order":4781,"tags":["AC","flag"],"version":2,"annotation":"flag: Ascension Island","shortcodes":["ascension_island","flag_ac"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ฉ","group":9,"order":4782,"tags":["AD","flag"],"version":2,"annotation":"flag: Andorra","shortcodes":["andorra","flag_ad"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ช","group":9,"order":4783,"tags":["AE","flag"],"version":2,"annotation":"flag: United Arab Emirates","shortcodes":["flag_ae","united_arab_emirates"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ซ","group":9,"order":4784,"tags":["AF","flag"],"version":2,"annotation":"flag: Afghanistan","shortcodes":["afghanistan","flag_af"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ฌ","group":9,"order":4785,"tags":["AG","flag"],"version":2,"annotation":"flag: Antigua & Barbuda","shortcodes":["antigua_barbuda","flag_ag"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ฎ","group":9,"order":4786,"tags":["AI","flag"],"version":2,"annotation":"flag: Anguilla","shortcodes":["anguilla","flag_ai"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ฑ","group":9,"order":4787,"tags":["AL","flag"],"version":2,"annotation":"flag: Albania","shortcodes":["albania","flag_al"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ฒ","group":9,"order":4788,"tags":["AM","flag"],"version":2,"annotation":"flag: Armenia","shortcodes":["armenia","flag_am"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ด","group":9,"order":4789,"tags":["AO","flag"],"version":2,"annotation":"flag: Angola","shortcodes":["angola","flag_ao"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ถ","group":9,"order":4790,"tags":["AQ","flag"],"version":2,"annotation":"flag: Antarctica","shortcodes":["antarctica","flag_aq"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ท","group":9,"order":4791,"tags":["AR","flag"],"version":2,"annotation":"flag: Argentina","shortcodes":["argentina","flag_ar"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ธ","group":9,"order":4792,"tags":["AS","flag"],"version":2,"annotation":"flag: American Samoa","shortcodes":["american_samoa","flag_as"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡น","group":9,"order":4793,"tags":["AT","flag"],"version":2,"annotation":"flag: Austria","shortcodes":["austria","flag_at"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡บ","group":9,"order":4794,"tags":["AU","flag"],"version":2,"annotation":"flag: Australia","shortcodes":["australia","flag_au"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ผ","group":9,"order":4795,"tags":["AW","flag"],"version":2,"annotation":"flag: Aruba","shortcodes":["aruba","flag_aw"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ฝ","group":9,"order":4796,"tags":["AX","flag"],"version":2,"annotation":"flag: ร…land Islands","shortcodes":["aland_islands","flag_ax"]},{"emoji":"๐Ÿ‡ฆ๐Ÿ‡ฟ","group":9,"order":4797,"tags":["AZ","flag"],"version":2,"annotation":"flag: Azerbaijan","shortcodes":["azerbaijan","flag_az"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ฆ","group":9,"order":4798,"tags":["BA","flag"],"version":2,"annotation":"flag: Bosnia & Herzegovina","shortcodes":["bosnia_herzegovina","flag_ba"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ง","group":9,"order":4799,"tags":["BB","flag"],"version":2,"annotation":"flag: Barbados","shortcodes":["barbados","flag_bb"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ฉ","group":9,"order":4800,"tags":["BD","flag"],"version":2,"annotation":"flag: Bangladesh","shortcodes":["bangladesh","flag_bd"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ช","group":9,"order":4801,"tags":["BE","flag"],"version":2,"annotation":"flag: Belgium","shortcodes":["belgium","flag_be"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ซ","group":9,"order":4802,"tags":["BF","flag"],"version":2,"annotation":"flag: Burkina Faso","shortcodes":["burkina_faso","flag_bf"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ฌ","group":9,"order":4803,"tags":["BG","flag"],"version":2,"annotation":"flag: Bulgaria","shortcodes":["bulgaria","flag_bg"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ญ","group":9,"order":4804,"tags":["BH","flag"],"version":2,"annotation":"flag: Bahrain","shortcodes":["bahrain","flag_bh"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ฎ","group":9,"order":4805,"tags":["BI","flag"],"version":2,"annotation":"flag: Burundi","shortcodes":["burundi","flag_bi"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ฏ","group":9,"order":4806,"tags":["BJ","flag"],"version":2,"annotation":"flag: Benin","shortcodes":["benin","flag_bj"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ฑ","group":9,"order":4807,"tags":["BL","flag"],"version":2,"annotation":"flag: St. Barthรฉlemy","shortcodes":["flag_bl","st_barthelemy"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ฒ","group":9,"order":4808,"tags":["BM","flag"],"version":2,"annotation":"flag: Bermuda","shortcodes":["bermuda","flag_bm"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ณ","group":9,"order":4809,"tags":["BN","flag"],"version":2,"annotation":"flag: Brunei","shortcodes":["brunei","flag_bn"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ด","group":9,"order":4810,"tags":["BO","flag"],"version":2,"annotation":"flag: Bolivia","shortcodes":["bolivia","flag_bo"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ถ","group":9,"order":4811,"tags":["BQ","flag"],"version":2,"annotation":"flag: Caribbean Netherlands","shortcodes":["caribbean_netherlands","flag_bq"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ท","group":9,"order":4812,"tags":["BR","flag"],"version":2,"annotation":"flag: Brazil","shortcodes":["brazil","flag_br"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ธ","group":9,"order":4813,"tags":["BS","flag"],"version":2,"annotation":"flag: Bahamas","shortcodes":["bahamas","flag_bs"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡น","group":9,"order":4814,"tags":["BT","flag"],"version":2,"annotation":"flag: Bhutan","shortcodes":["bhutan","flag_bt"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ป","group":9,"order":4815,"tags":["BV","flag"],"version":2,"annotation":"flag: Bouvet Island","shortcodes":["bouvet_island","flag_bv"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ผ","group":9,"order":4816,"tags":["BW","flag"],"version":2,"annotation":"flag: Botswana","shortcodes":["botswana","flag_bw"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡พ","group":9,"order":4817,"tags":["BY","flag"],"version":2,"annotation":"flag: Belarus","shortcodes":["belarus","flag_by"]},{"emoji":"๐Ÿ‡ง๐Ÿ‡ฟ","group":9,"order":4818,"tags":["BZ","flag"],"version":2,"annotation":"flag: Belize","shortcodes":["belize","flag_bz"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ฆ","group":9,"order":4819,"tags":["CA","flag"],"version":2,"annotation":"flag: Canada","shortcodes":["canada","flag_ca"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡จ","group":9,"order":4820,"tags":["CC","flag"],"version":2,"annotation":"flag: Cocos (Keeling) Islands","shortcodes":["cocos_islands","flag_cc"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ฉ","group":9,"order":4821,"tags":["CD","flag"],"version":2,"annotation":"flag: Congo - Kinshasa","shortcodes":["congo_kinshasa","flag_cd"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ซ","group":9,"order":4822,"tags":["CF","flag"],"version":2,"annotation":"flag: Central African Republic","shortcodes":["central_african_republic","flag_cf"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ฌ","group":9,"order":4823,"tags":["CG","flag"],"version":2,"annotation":"flag: Congo - Brazzaville","shortcodes":["congo_brazzaville","flag_cg"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ญ","group":9,"order":4824,"tags":["CH","flag"],"version":2,"annotation":"flag: Switzerland","shortcodes":["flag_ch","switzerland"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ฎ","group":9,"order":4825,"tags":["CI","flag"],"version":2,"annotation":"flag: Cรดte dโ€™Ivoire","shortcodes":["cote_divoire","flag_ci"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ฐ","group":9,"order":4826,"tags":["CK","flag"],"version":2,"annotation":"flag: Cook Islands","shortcodes":["cook_islands","flag_ck"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ฑ","group":9,"order":4827,"tags":["CL","flag"],"version":2,"annotation":"flag: Chile","shortcodes":["chile","flag_cl"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ฒ","group":9,"order":4828,"tags":["CM","flag"],"version":2,"annotation":"flag: Cameroon","shortcodes":["cameroon","flag_cm"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ณ","group":9,"order":4829,"tags":["CN","flag"],"version":0.6,"annotation":"flag: China","shortcodes":["china","flag_cn"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ด","group":9,"order":4830,"tags":["CO","flag"],"version":2,"annotation":"flag: Colombia","shortcodes":["colombia","flag_co"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ต","group":9,"order":4831,"tags":["CP","flag"],"version":2,"annotation":"flag: Clipperton Island","shortcodes":["clipperton_island","flag_cp"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ถ","group":9,"order":4832,"tags":["CQ","flag"],"version":16,"annotation":"flag: Sark","shortcodes":["flag_cq","sark"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ท","group":9,"order":4833,"tags":["CR","flag"],"version":2,"annotation":"flag: Costa Rica","shortcodes":["costa_rica","flag_cr"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡บ","group":9,"order":4834,"tags":["CU","flag"],"version":2,"annotation":"flag: Cuba","shortcodes":["cuba","flag_cu"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ป","group":9,"order":4835,"tags":["CV","flag"],"version":2,"annotation":"flag: Cape Verde","shortcodes":["cape_verde","flag_cv"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ผ","group":9,"order":4836,"tags":["CW","flag"],"version":2,"annotation":"flag: Curaรงao","shortcodes":["curacao","flag_cw"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ฝ","group":9,"order":4837,"tags":["CX","flag"],"version":2,"annotation":"flag: Christmas Island","shortcodes":["christmas_island","flag_cx"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡พ","group":9,"order":4838,"tags":["CY","flag"],"version":2,"annotation":"flag: Cyprus","shortcodes":["cyprus","flag_cy"]},{"emoji":"๐Ÿ‡จ๐Ÿ‡ฟ","group":9,"order":4839,"tags":["CZ","flag"],"version":2,"annotation":"flag: Czechia","shortcodes":["czech_republic","czechia","flag_cz"]},{"emoji":"๐Ÿ‡ฉ๐Ÿ‡ช","group":9,"order":4840,"tags":["DE","flag"],"version":0.6,"annotation":"flag: Germany","shortcodes":["flag_de","germany"]},{"emoji":"๐Ÿ‡ฉ๐Ÿ‡ฌ","group":9,"order":4841,"tags":["DG","flag"],"version":2,"annotation":"flag: Diego Garcia","shortcodes":["diego_garcia","flag_dg"]},{"emoji":"๐Ÿ‡ฉ๐Ÿ‡ฏ","group":9,"order":4842,"tags":["DJ","flag"],"version":2,"annotation":"flag: Djibouti","shortcodes":["djibouti","flag_dj"]},{"emoji":"๐Ÿ‡ฉ๐Ÿ‡ฐ","group":9,"order":4843,"tags":["DK","flag"],"version":2,"annotation":"flag: Denmark","shortcodes":["denmark","flag_dk"]},{"emoji":"๐Ÿ‡ฉ๐Ÿ‡ฒ","group":9,"order":4844,"tags":["DM","flag"],"version":2,"annotation":"flag: Dominica","shortcodes":["dominica","flag_dm"]},{"emoji":"๐Ÿ‡ฉ๐Ÿ‡ด","group":9,"order":4845,"tags":["DO","flag"],"version":2,"annotation":"flag: Dominican Republic","shortcodes":["dominican_republic","flag_do"]},{"emoji":"๐Ÿ‡ฉ๐Ÿ‡ฟ","group":9,"order":4846,"tags":["DZ","flag"],"version":2,"annotation":"flag: Algeria","shortcodes":["algeria","flag_dz"]},{"emoji":"๐Ÿ‡ช๐Ÿ‡ฆ","group":9,"order":4847,"tags":["EA","flag"],"version":2,"annotation":"flag: Ceuta & Melilla","shortcodes":["ceuta_melilla","flag_ea"]},{"emoji":"๐Ÿ‡ช๐Ÿ‡จ","group":9,"order":4848,"tags":["EC","flag"],"version":2,"annotation":"flag: Ecuador","shortcodes":["ecuador","flag_ec"]},{"emoji":"๐Ÿ‡ช๐Ÿ‡ช","group":9,"order":4849,"tags":["EE","flag"],"version":2,"annotation":"flag: Estonia","shortcodes":["estonia","flag_ee"]},{"emoji":"๐Ÿ‡ช๐Ÿ‡ฌ","group":9,"order":4850,"tags":["EG","flag"],"version":2,"annotation":"flag: Egypt","shortcodes":["egypt","flag_eg"]},{"emoji":"๐Ÿ‡ช๐Ÿ‡ญ","group":9,"order":4851,"tags":["EH","flag"],"version":2,"annotation":"flag: Western Sahara","shortcodes":["flag_eh","western_sahara"]},{"emoji":"๐Ÿ‡ช๐Ÿ‡ท","group":9,"order":4852,"tags":["ER","flag"],"version":2,"annotation":"flag: Eritrea","shortcodes":["eritrea","flag_er"]},{"emoji":"๐Ÿ‡ช๐Ÿ‡ธ","group":9,"order":4853,"tags":["ES","flag"],"version":0.6,"annotation":"flag: Spain","shortcodes":["flag_es","spain"]},{"emoji":"๐Ÿ‡ช๐Ÿ‡น","group":9,"order":4854,"tags":["ET","flag"],"version":2,"annotation":"flag: Ethiopia","shortcodes":["ethiopia","flag_et"]},{"emoji":"๐Ÿ‡ช๐Ÿ‡บ","group":9,"order":4855,"tags":["EU","flag"],"version":2,"annotation":"flag: European Union","shortcodes":["european_union","flag_eu"]},{"emoji":"๐Ÿ‡ซ๐Ÿ‡ฎ","group":9,"order":4856,"tags":["FI","flag"],"version":2,"annotation":"flag: Finland","shortcodes":["finland","flag_fi"]},{"emoji":"๐Ÿ‡ซ๐Ÿ‡ฏ","group":9,"order":4857,"tags":["FJ","flag"],"version":2,"annotation":"flag: Fiji","shortcodes":["fiji","flag_fj"]},{"emoji":"๐Ÿ‡ซ๐Ÿ‡ฐ","group":9,"order":4858,"tags":["FK","flag"],"version":2,"annotation":"flag: Falkland Islands","shortcodes":["falkland_islands","flag_fk"]},{"emoji":"๐Ÿ‡ซ๐Ÿ‡ฒ","group":9,"order":4859,"tags":["FM","flag"],"version":2,"annotation":"flag: Micronesia","shortcodes":["flag_fm","micronesia"]},{"emoji":"๐Ÿ‡ซ๐Ÿ‡ด","group":9,"order":4860,"tags":["FO","flag"],"version":2,"annotation":"flag: Faroe Islands","shortcodes":["faroe_islands","flag_fo"]},{"emoji":"๐Ÿ‡ซ๐Ÿ‡ท","group":9,"order":4861,"tags":["FR","flag"],"version":0.6,"annotation":"flag: France","shortcodes":["flag_fr","france"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ฆ","group":9,"order":4862,"tags":["GA","flag"],"version":2,"annotation":"flag: Gabon","shortcodes":["flag_ga","gabon"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ง","group":9,"order":4863,"tags":["GB","flag"],"version":0.6,"annotation":"flag: United Kingdom","shortcodes":["flag_gb","uk","united_kingdom"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ฉ","group":9,"order":4864,"tags":["GD","flag"],"version":2,"annotation":"flag: Grenada","shortcodes":["flag_gd","grenada"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ช","group":9,"order":4865,"tags":["GE","flag"],"version":2,"annotation":"flag: Georgia","shortcodes":["flag_ge","georgia"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ซ","group":9,"order":4866,"tags":["GF","flag"],"version":2,"annotation":"flag: French Guiana","shortcodes":["flag_gf","french_guiana"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ฌ","group":9,"order":4867,"tags":["GG","flag"],"version":2,"annotation":"flag: Guernsey","shortcodes":["flag_gg","guernsey"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ญ","group":9,"order":4868,"tags":["GH","flag"],"version":2,"annotation":"flag: Ghana","shortcodes":["flag_gh","ghana"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ฎ","group":9,"order":4869,"tags":["GI","flag"],"version":2,"annotation":"flag: Gibraltar","shortcodes":["flag_gi","gibraltar"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ฑ","group":9,"order":4870,"tags":["GL","flag"],"version":2,"annotation":"flag: Greenland","shortcodes":["flag_gl","greenland"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ฒ","group":9,"order":4871,"tags":["GM","flag"],"version":2,"annotation":"flag: Gambia","shortcodes":["flag_gm","gambia"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ณ","group":9,"order":4872,"tags":["GN","flag"],"version":2,"annotation":"flag: Guinea","shortcodes":["flag_gn","guinea"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ต","group":9,"order":4873,"tags":["GP","flag"],"version":2,"annotation":"flag: Guadeloupe","shortcodes":["flag_gp","guadeloupe"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ถ","group":9,"order":4874,"tags":["GQ","flag"],"version":2,"annotation":"flag: Equatorial Guinea","shortcodes":["equatorial_guinea","flag_gq"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ท","group":9,"order":4875,"tags":["GR","flag"],"version":2,"annotation":"flag: Greece","shortcodes":["flag_gr","greece"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ธ","group":9,"order":4876,"tags":["GS","flag"],"version":2,"annotation":"flag: South Georgia & South Sandwich Islands","shortcodes":["flag_gs","south_georgia_south_sandwich_islands"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡น","group":9,"order":4877,"tags":["GT","flag"],"version":2,"annotation":"flag: Guatemala","shortcodes":["flag_gt","guatemala"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡บ","group":9,"order":4878,"tags":["GU","flag"],"version":2,"annotation":"flag: Guam","shortcodes":["flag_gu","guam"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡ผ","group":9,"order":4879,"tags":["GW","flag"],"version":2,"annotation":"flag: Guinea-Bissau","shortcodes":["flag_gw","guinea_bissau"]},{"emoji":"๐Ÿ‡ฌ๐Ÿ‡พ","group":9,"order":4880,"tags":["GY","flag"],"version":2,"annotation":"flag: Guyana","shortcodes":["flag_gy","guyana"]},{"emoji":"๐Ÿ‡ญ๐Ÿ‡ฐ","group":9,"order":4881,"tags":["HK","flag"],"version":2,"annotation":"flag: Hong Kong SAR China","shortcodes":["flag_hk","hong_kong"]},{"emoji":"๐Ÿ‡ญ๐Ÿ‡ฒ","group":9,"order":4882,"tags":["HM","flag"],"version":2,"annotation":"flag: Heard & McDonald Islands","shortcodes":["flag_hm","heard_mcdonald_islands"]},{"emoji":"๐Ÿ‡ญ๐Ÿ‡ณ","group":9,"order":4883,"tags":["HN","flag"],"version":2,"annotation":"flag: Honduras","shortcodes":["flag_hn","honduras"]},{"emoji":"๐Ÿ‡ญ๐Ÿ‡ท","group":9,"order":4884,"tags":["HR","flag"],"version":2,"annotation":"flag: Croatia","shortcodes":["croatia","flag_hr"]},{"emoji":"๐Ÿ‡ญ๐Ÿ‡น","group":9,"order":4885,"tags":["HT","flag"],"version":2,"annotation":"flag: Haiti","shortcodes":["flag_ht","haiti"]},{"emoji":"๐Ÿ‡ญ๐Ÿ‡บ","group":9,"order":4886,"tags":["HU","flag"],"version":2,"annotation":"flag: Hungary","shortcodes":["flag_hu","hungary"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡จ","group":9,"order":4887,"tags":["IC","flag"],"version":2,"annotation":"flag: Canary Islands","shortcodes":["canary_islands","flag_ic"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡ฉ","group":9,"order":4888,"tags":["ID","flag"],"version":2,"annotation":"flag: Indonesia","shortcodes":["flag_id","indonesia"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡ช","group":9,"order":4889,"tags":["IE","flag"],"version":2,"annotation":"flag: Ireland","shortcodes":["flag_ie","ireland"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡ฑ","group":9,"order":4890,"tags":["IL","flag"],"version":2,"annotation":"flag: Israel","shortcodes":["flag_il","israel"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡ฒ","group":9,"order":4891,"tags":["IM","flag"],"version":2,"annotation":"flag: Isle of Man","shortcodes":["flag_im","isle_of_man"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡ณ","group":9,"order":4892,"tags":["IN","flag"],"version":2,"annotation":"flag: India","shortcodes":["flag_in","india"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡ด","group":9,"order":4893,"tags":["IO","flag"],"version":2,"annotation":"flag: British Indian Ocean Territory","shortcodes":["british_indian_ocean_territory","flag_io"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡ถ","group":9,"order":4894,"tags":["IQ","flag"],"version":2,"annotation":"flag: Iraq","shortcodes":["flag_iq","iraq"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡ท","group":9,"order":4895,"tags":["IR","flag"],"version":2,"annotation":"flag: Iran","shortcodes":["flag_ir","iran"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡ธ","group":9,"order":4896,"tags":["IS","flag"],"version":2,"annotation":"flag: Iceland","shortcodes":["flag_is","iceland"]},{"emoji":"๐Ÿ‡ฎ๐Ÿ‡น","group":9,"order":4897,"tags":["IT","flag"],"version":0.6,"annotation":"flag: Italy","shortcodes":["flag_it","italy"]},{"emoji":"๐Ÿ‡ฏ๐Ÿ‡ช","group":9,"order":4898,"tags":["JE","flag"],"version":2,"annotation":"flag: Jersey","shortcodes":["flag_je","jersey"]},{"emoji":"๐Ÿ‡ฏ๐Ÿ‡ฒ","group":9,"order":4899,"tags":["JM","flag"],"version":2,"annotation":"flag: Jamaica","shortcodes":["flag_jm","jamaica"]},{"emoji":"๐Ÿ‡ฏ๐Ÿ‡ด","group":9,"order":4900,"tags":["JO","flag"],"version":2,"annotation":"flag: Jordan","shortcodes":["flag_jo","jordan"]},{"emoji":"๐Ÿ‡ฏ๐Ÿ‡ต","group":9,"order":4901,"tags":["JP","flag"],"version":0.6,"annotation":"flag: Japan","shortcodes":["flag_jp","japan"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ช","group":9,"order":4902,"tags":["KE","flag"],"version":2,"annotation":"flag: Kenya","shortcodes":["flag_ke","kenya"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ฌ","group":9,"order":4903,"tags":["KG","flag"],"version":2,"annotation":"flag: Kyrgyzstan","shortcodes":["flag_kg","kyrgyzstan"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ญ","group":9,"order":4904,"tags":["KH","flag"],"version":2,"annotation":"flag: Cambodia","shortcodes":["cambodia","flag_kh"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ฎ","group":9,"order":4905,"tags":["KI","flag"],"version":2,"annotation":"flag: Kiribati","shortcodes":["flag_ki","kiribati"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ฒ","group":9,"order":4906,"tags":["KM","flag"],"version":2,"annotation":"flag: Comoros","shortcodes":["comoros","flag_km"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ณ","group":9,"order":4907,"tags":["KN","flag"],"version":2,"annotation":"flag: St. Kitts & Nevis","shortcodes":["flag_kn","st_kitts_nevis"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ต","group":9,"order":4908,"tags":["KP","flag"],"version":2,"annotation":"flag: North Korea","shortcodes":["flag_kp","north_korea"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ท","group":9,"order":4909,"tags":["KR","flag"],"version":0.6,"annotation":"flag: South Korea","shortcodes":["flag_kr","south_korea"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ผ","group":9,"order":4910,"tags":["KW","flag"],"version":2,"annotation":"flag: Kuwait","shortcodes":["flag_kw","kuwait"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡พ","group":9,"order":4911,"tags":["KY","flag"],"version":2,"annotation":"flag: Cayman Islands","shortcodes":["cayman_islands","flag_ky"]},{"emoji":"๐Ÿ‡ฐ๐Ÿ‡ฟ","group":9,"order":4912,"tags":["KZ","flag"],"version":2,"annotation":"flag: Kazakhstan","shortcodes":["flag_kz","kazakhstan"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡ฆ","group":9,"order":4913,"tags":["LA","flag"],"version":2,"annotation":"flag: Laos","shortcodes":["flag_la","laos"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡ง","group":9,"order":4914,"tags":["LB","flag"],"version":2,"annotation":"flag: Lebanon","shortcodes":["flag_lb","lebanon"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡จ","group":9,"order":4915,"tags":["LC","flag"],"version":2,"annotation":"flag: St. Lucia","shortcodes":["flag_lc","st_lucia"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡ฎ","group":9,"order":4916,"tags":["LI","flag"],"version":2,"annotation":"flag: Liechtenstein","shortcodes":["flag_li","liechtenstein"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡ฐ","group":9,"order":4917,"tags":["LK","flag"],"version":2,"annotation":"flag: Sri Lanka","shortcodes":["flag_lk","sri_lanka"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡ท","group":9,"order":4918,"tags":["LR","flag"],"version":2,"annotation":"flag: Liberia","shortcodes":["flag_lr","liberia"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡ธ","group":9,"order":4919,"tags":["LS","flag"],"version":2,"annotation":"flag: Lesotho","shortcodes":["flag_ls","lesotho"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡น","group":9,"order":4920,"tags":["LT","flag"],"version":2,"annotation":"flag: Lithuania","shortcodes":["flag_lt","lithuania"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡บ","group":9,"order":4921,"tags":["LU","flag"],"version":2,"annotation":"flag: Luxembourg","shortcodes":["flag_lu","luxembourg"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡ป","group":9,"order":4922,"tags":["LV","flag"],"version":2,"annotation":"flag: Latvia","shortcodes":["flag_lv","latvia"]},{"emoji":"๐Ÿ‡ฑ๐Ÿ‡พ","group":9,"order":4923,"tags":["LY","flag"],"version":2,"annotation":"flag: Libya","shortcodes":["flag_ly","libya"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ฆ","group":9,"order":4924,"tags":["MA","flag"],"version":2,"annotation":"flag: Morocco","shortcodes":["flag_ma","morocco"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡จ","group":9,"order":4925,"tags":["MC","flag"],"version":2,"annotation":"flag: Monaco","shortcodes":["flag_mc","monaco"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ฉ","group":9,"order":4926,"tags":["MD","flag"],"version":2,"annotation":"flag: Moldova","shortcodes":["flag_md","moldova"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ช","group":9,"order":4927,"tags":["ME","flag"],"version":2,"annotation":"flag: Montenegro","shortcodes":["flag_me","montenegro"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ซ","group":9,"order":4928,"tags":["MF","flag"],"version":2,"annotation":"flag: St. Martin","shortcodes":["flag_mf","st_martin"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ฌ","group":9,"order":4929,"tags":["MG","flag"],"version":2,"annotation":"flag: Madagascar","shortcodes":["flag_mg","madagascar"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ญ","group":9,"order":4930,"tags":["MH","flag"],"version":2,"annotation":"flag: Marshall Islands","shortcodes":["flag_mh","marshall_islands"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ฐ","group":9,"order":4931,"tags":["MK","flag"],"version":2,"annotation":"flag: North Macedonia","shortcodes":["flag_mk","macedonia"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ฑ","group":9,"order":4932,"tags":["ML","flag"],"version":2,"annotation":"flag: Mali","shortcodes":["flag_ml","mali"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ฒ","group":9,"order":4933,"tags":["MM","flag"],"version":2,"annotation":"flag: Myanmar (Burma)","shortcodes":["burma","flag_mm","myanmar"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ณ","group":9,"order":4934,"tags":["MN","flag"],"version":2,"annotation":"flag: Mongolia","shortcodes":["flag_mn","mongolia"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ด","group":9,"order":4935,"tags":["MO","flag"],"version":2,"annotation":"flag: Macao SAR China","shortcodes":["flag_mo","macao","macau"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ต","group":9,"order":4936,"tags":["MP","flag"],"version":2,"annotation":"flag: Northern Mariana Islands","shortcodes":["flag_mp","northern_mariana_islands"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ถ","group":9,"order":4937,"tags":["MQ","flag"],"version":2,"annotation":"flag: Martinique","shortcodes":["flag_mq","martinique"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ท","group":9,"order":4938,"tags":["MR","flag"],"version":2,"annotation":"flag: Mauritania","shortcodes":["flag_mr","mauritania"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ธ","group":9,"order":4939,"tags":["MS","flag"],"version":2,"annotation":"flag: Montserrat","shortcodes":["flag_ms","montserrat"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡น","group":9,"order":4940,"tags":["MT","flag"],"version":2,"annotation":"flag: Malta","shortcodes":["flag_mt","malta"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡บ","group":9,"order":4941,"tags":["MU","flag"],"version":2,"annotation":"flag: Mauritius","shortcodes":["flag_mu","mauritius"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ป","group":9,"order":4942,"tags":["MV","flag"],"version":2,"annotation":"flag: Maldives","shortcodes":["flag_mv","maldives"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ผ","group":9,"order":4943,"tags":["MW","flag"],"version":2,"annotation":"flag: Malawi","shortcodes":["flag_mw","malawi"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ฝ","group":9,"order":4944,"tags":["MX","flag"],"version":2,"annotation":"flag: Mexico","shortcodes":["flag_mx","mexico"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡พ","group":9,"order":4945,"tags":["MY","flag"],"version":2,"annotation":"flag: Malaysia","shortcodes":["flag_my","malaysia"]},{"emoji":"๐Ÿ‡ฒ๐Ÿ‡ฟ","group":9,"order":4946,"tags":["MZ","flag"],"version":2,"annotation":"flag: Mozambique","shortcodes":["flag_mz","mozambique"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ฆ","group":9,"order":4947,"tags":["NA","flag"],"version":2,"annotation":"flag: Namibia","shortcodes":["flag_na","namibia"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡จ","group":9,"order":4948,"tags":["NC","flag"],"version":2,"annotation":"flag: New Caledonia","shortcodes":["flag_nc","new_caledonia"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ช","group":9,"order":4949,"tags":["NE","flag"],"version":2,"annotation":"flag: Niger","shortcodes":["flag_ne","niger"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ซ","group":9,"order":4950,"tags":["NF","flag"],"version":2,"annotation":"flag: Norfolk Island","shortcodes":["flag_nf","norfolk_island"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ฌ","group":9,"order":4951,"tags":["NG","flag"],"version":2,"annotation":"flag: Nigeria","shortcodes":["flag_ng","nigeria"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ฎ","group":9,"order":4952,"tags":["NI","flag"],"version":2,"annotation":"flag: Nicaragua","shortcodes":["flag_ni","nicaragua"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ฑ","group":9,"order":4953,"tags":["NL","flag"],"version":2,"annotation":"flag: Netherlands","shortcodes":["flag_nl","netherlands"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ด","group":9,"order":4954,"tags":["NO","flag"],"version":2,"annotation":"flag: Norway","shortcodes":["flag_no","norway"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ต","group":9,"order":4955,"tags":["NP","flag"],"version":2,"annotation":"flag: Nepal","shortcodes":["flag_np","nepal"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ท","group":9,"order":4956,"tags":["NR","flag"],"version":2,"annotation":"flag: Nauru","shortcodes":["flag_nr","nauru"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡บ","group":9,"order":4957,"tags":["NU","flag"],"version":2,"annotation":"flag: Niue","shortcodes":["flag_nu","niue"]},{"emoji":"๐Ÿ‡ณ๐Ÿ‡ฟ","group":9,"order":4958,"tags":["NZ","flag"],"version":2,"annotation":"flag: New Zealand","shortcodes":["flag_nz","new_zealand"]},{"emoji":"๐Ÿ‡ด๐Ÿ‡ฒ","group":9,"order":4959,"tags":["OM","flag"],"version":2,"annotation":"flag: Oman","shortcodes":["flag_om","oman"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ฆ","group":9,"order":4960,"tags":["PA","flag"],"version":2,"annotation":"flag: Panama","shortcodes":["flag_pa","panama"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ช","group":9,"order":4961,"tags":["PE","flag"],"version":2,"annotation":"flag: Peru","shortcodes":["flag_pe","peru"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ซ","group":9,"order":4962,"tags":["PF","flag"],"version":2,"annotation":"flag: French Polynesia","shortcodes":["flag_pf","french_polynesia"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ฌ","group":9,"order":4963,"tags":["PG","flag"],"version":2,"annotation":"flag: Papua New Guinea","shortcodes":["flag_pg","papua_new_guinea"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ญ","group":9,"order":4964,"tags":["PH","flag"],"version":2,"annotation":"flag: Philippines","shortcodes":["flag_ph","philippines"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ฐ","group":9,"order":4965,"tags":["PK","flag"],"version":2,"annotation":"flag: Pakistan","shortcodes":["flag_pk","pakistan"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ฑ","group":9,"order":4966,"tags":["PL","flag"],"version":2,"annotation":"flag: Poland","shortcodes":["flag_pl","poland"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ฒ","group":9,"order":4967,"tags":["PM","flag"],"version":2,"annotation":"flag: St. Pierre & Miquelon","shortcodes":["flag_pm","st_pierre_miquelon"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ณ","group":9,"order":4968,"tags":["PN","flag"],"version":2,"annotation":"flag: Pitcairn Islands","shortcodes":["flag_pn","pitcairn_islands"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ท","group":9,"order":4969,"tags":["PR","flag"],"version":2,"annotation":"flag: Puerto Rico","shortcodes":["flag_pr","puerto_rico"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ธ","group":9,"order":4970,"tags":["PS","flag"],"version":2,"annotation":"flag: Palestinian Territories","shortcodes":["flag_ps","palestinian_territories"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡น","group":9,"order":4971,"tags":["PT","flag"],"version":2,"annotation":"flag: Portugal","shortcodes":["flag_pt","portugal"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡ผ","group":9,"order":4972,"tags":["PW","flag"],"version":2,"annotation":"flag: Palau","shortcodes":["flag_pw","palau"]},{"emoji":"๐Ÿ‡ต๐Ÿ‡พ","group":9,"order":4973,"tags":["PY","flag"],"version":2,"annotation":"flag: Paraguay","shortcodes":["flag_py","paraguay"]},{"emoji":"๐Ÿ‡ถ๐Ÿ‡ฆ","group":9,"order":4974,"tags":["QA","flag"],"version":2,"annotation":"flag: Qatar","shortcodes":["flag_qa","qatar"]},{"emoji":"๐Ÿ‡ท๐Ÿ‡ช","group":9,"order":4975,"tags":["RE","flag"],"version":2,"annotation":"flag: Rรฉunion","shortcodes":["flag_re","reunion"]},{"emoji":"๐Ÿ‡ท๐Ÿ‡ด","group":9,"order":4976,"tags":["RO","flag"],"version":2,"annotation":"flag: Romania","shortcodes":["flag_ro","romania"]},{"emoji":"๐Ÿ‡ท๐Ÿ‡ธ","group":9,"order":4977,"tags":["RS","flag"],"version":2,"annotation":"flag: Serbia","shortcodes":["flag_rs","serbia"]},{"emoji":"๐Ÿ‡ท๐Ÿ‡บ","group":9,"order":4978,"tags":["RU","flag"],"version":0.6,"annotation":"flag: Russia","shortcodes":["flag_ru","russia"]},{"emoji":"๐Ÿ‡ท๐Ÿ‡ผ","group":9,"order":4979,"tags":["RW","flag"],"version":2,"annotation":"flag: Rwanda","shortcodes":["flag_rw","rwanda"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฆ","group":9,"order":4980,"tags":["SA","flag"],"version":2,"annotation":"flag: Saudi Arabia","shortcodes":["flag_sa","saudi_arabia"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ง","group":9,"order":4981,"tags":["SB","flag"],"version":2,"annotation":"flag: Solomon Islands","shortcodes":["flag_sb","solomon_islands"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡จ","group":9,"order":4982,"tags":["SC","flag"],"version":2,"annotation":"flag: Seychelles","shortcodes":["flag_sc","seychelles"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฉ","group":9,"order":4983,"tags":["SD","flag"],"version":2,"annotation":"flag: Sudan","shortcodes":["flag_sd","sudan"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ช","group":9,"order":4984,"tags":["SE","flag"],"version":2,"annotation":"flag: Sweden","shortcodes":["flag_se","sweden"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฌ","group":9,"order":4985,"tags":["SG","flag"],"version":2,"annotation":"flag: Singapore","shortcodes":["flag_sg","singapore"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ญ","group":9,"order":4986,"tags":["SH","flag"],"version":2,"annotation":"flag: St. Helena","shortcodes":["flag_sh","st_helena"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฎ","group":9,"order":4987,"tags":["SI","flag"],"version":2,"annotation":"flag: Slovenia","shortcodes":["flag_si","slovenia"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฏ","group":9,"order":4988,"tags":["SJ","flag"],"version":2,"annotation":"flag: Svalbard & Jan Mayen","shortcodes":["flag_sj","svalbard_jan_mayen"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฐ","group":9,"order":4989,"tags":["SK","flag"],"version":2,"annotation":"flag: Slovakia","shortcodes":["flag_sk","slovakia"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฑ","group":9,"order":4990,"tags":["SL","flag"],"version":2,"annotation":"flag: Sierra Leone","shortcodes":["flag_sl","sierra_leone"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฒ","group":9,"order":4991,"tags":["SM","flag"],"version":2,"annotation":"flag: San Marino","shortcodes":["flag_sm","san_marino"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ณ","group":9,"order":4992,"tags":["SN","flag"],"version":2,"annotation":"flag: Senegal","shortcodes":["flag_sn","senegal"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ด","group":9,"order":4993,"tags":["SO","flag"],"version":2,"annotation":"flag: Somalia","shortcodes":["flag_so","somalia"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ท","group":9,"order":4994,"tags":["SR","flag"],"version":2,"annotation":"flag: Suriname","shortcodes":["flag_sr","suriname"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ธ","group":9,"order":4995,"tags":["SS","flag"],"version":2,"annotation":"flag: South Sudan","shortcodes":["flag_ss","south_sudan"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡น","group":9,"order":4996,"tags":["ST","flag"],"version":2,"annotation":"flag: Sรฃo Tomรฉ & Prรญncipe","shortcodes":["flag_st","sao_tome_principe"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ป","group":9,"order":4997,"tags":["SV","flag"],"version":2,"annotation":"flag: El Salvador","shortcodes":["el_salvador","flag_sv"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฝ","group":9,"order":4998,"tags":["SX","flag"],"version":2,"annotation":"flag: Sint Maarten","shortcodes":["flag_sx","sint_maarten"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡พ","group":9,"order":4999,"tags":["SY","flag"],"version":2,"annotation":"flag: Syria","shortcodes":["flag_sy","syria"]},{"emoji":"๐Ÿ‡ธ๐Ÿ‡ฟ","group":9,"order":5000,"tags":["SZ","flag"],"version":2,"annotation":"flag: Eswatini","shortcodes":["eswatini","flag_sz","swaziland"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ฆ","group":9,"order":5001,"tags":["TA","flag"],"version":2,"annotation":"flag: Tristan da Cunha","shortcodes":["flag_ta","tristan_da_cunha"]},{"emoji":"๐Ÿ‡น๐Ÿ‡จ","group":9,"order":5002,"tags":["TC","flag"],"version":2,"annotation":"flag: Turks & Caicos Islands","shortcodes":["flag_tc","turks_caicos_islands"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ฉ","group":9,"order":5003,"tags":["TD","flag"],"version":2,"annotation":"flag: Chad","shortcodes":["chad","flag_td"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ซ","group":9,"order":5004,"tags":["TF","flag"],"version":2,"annotation":"flag: French Southern Territories","shortcodes":["flag_tf","french_southern_territories"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ฌ","group":9,"order":5005,"tags":["TG","flag"],"version":2,"annotation":"flag: Togo","shortcodes":["flag_tg","togo"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ญ","group":9,"order":5006,"tags":["TH","flag"],"version":2,"annotation":"flag: Thailand","shortcodes":["flag_th","thailand"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ฏ","group":9,"order":5007,"tags":["TJ","flag"],"version":2,"annotation":"flag: Tajikistan","shortcodes":["flag_tj","tajikistan"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ฐ","group":9,"order":5008,"tags":["TK","flag"],"version":2,"annotation":"flag: Tokelau","shortcodes":["flag_tk","tokelau"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ฑ","group":9,"order":5009,"tags":["TL","flag"],"version":2,"annotation":"flag: Timor-Leste","shortcodes":["flag_tl","timor_leste"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ฒ","group":9,"order":5010,"tags":["TM","flag"],"version":2,"annotation":"flag: Turkmenistan","shortcodes":["flag_tm","turkmenistan"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ณ","group":9,"order":5011,"tags":["TN","flag"],"version":2,"annotation":"flag: Tunisia","shortcodes":["flag_tn","tunisia"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ด","group":9,"order":5012,"tags":["TO","flag"],"version":2,"annotation":"flag: Tonga","shortcodes":["flag_to","tonga"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ท","group":9,"order":5013,"tags":["TR","flag"],"version":2,"annotation":"flag: Tรผrkiye","shortcodes":["flag_tr","turkey_tr"]},{"emoji":"๐Ÿ‡น๐Ÿ‡น","group":9,"order":5014,"tags":["TT","flag"],"version":2,"annotation":"flag: Trinidad & Tobago","shortcodes":["flag_tt","trinidad_tobago"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ป","group":9,"order":5015,"tags":["TV","flag"],"version":2,"annotation":"flag: Tuvalu","shortcodes":["flag_tv","tuvalu"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ผ","group":9,"order":5016,"tags":["TW","flag"],"version":2,"annotation":"flag: Taiwan","shortcodes":["flag_tw","taiwan"]},{"emoji":"๐Ÿ‡น๐Ÿ‡ฟ","group":9,"order":5017,"tags":["TZ","flag"],"version":2,"annotation":"flag: Tanzania","shortcodes":["flag_tz","tanzania"]},{"emoji":"๐Ÿ‡บ๐Ÿ‡ฆ","group":9,"order":5018,"tags":["UA","flag"],"version":2,"annotation":"flag: Ukraine","shortcodes":["flag_ua","ukraine"]},{"emoji":"๐Ÿ‡บ๐Ÿ‡ฌ","group":9,"order":5019,"tags":["UG","flag"],"version":2,"annotation":"flag: Uganda","shortcodes":["flag_ug","uganda"]},{"emoji":"๐Ÿ‡บ๐Ÿ‡ฒ","group":9,"order":5020,"tags":["UM","flag"],"version":2,"annotation":"flag: U.S. Outlying Islands","shortcodes":["flag_um","us_outlying_islands"]},{"emoji":"๐Ÿ‡บ๐Ÿ‡ณ","group":9,"order":5021,"tags":["UN","flag"],"version":4,"annotation":"flag: United Nations","shortcodes":["flag_un","un","united_nations"]},{"emoji":"๐Ÿ‡บ๐Ÿ‡ธ","group":9,"order":5022,"tags":["US","flag"],"version":0.6,"annotation":"flag: United States","shortcodes":["flag_us","united_states","usa"]},{"emoji":"๐Ÿ‡บ๐Ÿ‡พ","group":9,"order":5023,"tags":["UY","flag"],"version":2,"annotation":"flag: Uruguay","shortcodes":["flag_uy","uruguay"]},{"emoji":"๐Ÿ‡บ๐Ÿ‡ฟ","group":9,"order":5024,"tags":["UZ","flag"],"version":2,"annotation":"flag: Uzbekistan","shortcodes":["flag_uz","uzbekistan"]},{"emoji":"๐Ÿ‡ป๐Ÿ‡ฆ","group":9,"order":5025,"tags":["VA","flag"],"version":2,"annotation":"flag: Vatican City","shortcodes":["flag_va","vatican_city"]},{"emoji":"๐Ÿ‡ป๐Ÿ‡จ","group":9,"order":5026,"tags":["VC","flag"],"version":2,"annotation":"flag: St. Vincent & Grenadines","shortcodes":["flag_vc","st_vincent_grenadines"]},{"emoji":"๐Ÿ‡ป๐Ÿ‡ช","group":9,"order":5027,"tags":["VE","flag"],"version":2,"annotation":"flag: Venezuela","shortcodes":["flag_ve","venezuela"]},{"emoji":"๐Ÿ‡ป๐Ÿ‡ฌ","group":9,"order":5028,"tags":["VG","flag"],"version":2,"annotation":"flag: British Virgin Islands","shortcodes":["british_virgin_islands","flag_vg"]},{"emoji":"๐Ÿ‡ป๐Ÿ‡ฎ","group":9,"order":5029,"tags":["VI","flag"],"version":2,"annotation":"flag: U.S. Virgin Islands","shortcodes":["flag_vi","us_virgin_islands"]},{"emoji":"๐Ÿ‡ป๐Ÿ‡ณ","group":9,"order":5030,"tags":["VN","flag"],"version":2,"annotation":"flag: Vietnam","shortcodes":["flag_vn","vietnam"]},{"emoji":"๐Ÿ‡ป๐Ÿ‡บ","group":9,"order":5031,"tags":["VU","flag"],"version":2,"annotation":"flag: Vanuatu","shortcodes":["flag_vu","vanuatu"]},{"emoji":"๐Ÿ‡ผ๐Ÿ‡ซ","group":9,"order":5032,"tags":["WF","flag"],"version":2,"annotation":"flag: Wallis & Futuna","shortcodes":["flag_wf","wallis_futuna"]},{"emoji":"๐Ÿ‡ผ๐Ÿ‡ธ","group":9,"order":5033,"tags":["WS","flag"],"version":2,"annotation":"flag: Samoa","shortcodes":["flag_ws","samoa"]},{"emoji":"๐Ÿ‡ฝ๐Ÿ‡ฐ","group":9,"order":5034,"tags":["XK","flag"],"version":2,"annotation":"flag: Kosovo","shortcodes":["flag_xk","kosovo"]},{"emoji":"๐Ÿ‡พ๐Ÿ‡ช","group":9,"order":5035,"tags":["YE","flag"],"version":2,"annotation":"flag: Yemen","shortcodes":["flag_ye","yemen"]},{"emoji":"๐Ÿ‡พ๐Ÿ‡น","group":9,"order":5036,"tags":["YT","flag"],"version":2,"annotation":"flag: Mayotte","shortcodes":["flag_yt","mayotte"]},{"emoji":"๐Ÿ‡ฟ๐Ÿ‡ฆ","group":9,"order":5037,"tags":["ZA","flag"],"version":2,"annotation":"flag: South Africa","shortcodes":["flag_za","south_africa"]},{"emoji":"๐Ÿ‡ฟ๐Ÿ‡ฒ","group":9,"order":5038,"tags":["ZM","flag"],"version":2,"annotation":"flag: Zambia","shortcodes":["flag_zm","zambia"]},{"emoji":"๐Ÿ‡ฟ๐Ÿ‡ผ","group":9,"order":5039,"tags":["ZW","flag"],"version":2,"annotation":"flag: Zimbabwe","shortcodes":["flag_zw","zimbabwe"]},{"emoji":"๐Ÿด๓ ง๓ ข๓ ฅ๓ ฎ๓ ง๓ ฟ","group":9,"order":5040,"tags":["flag","gbeng"],"version":5,"annotation":"flag: England","shortcodes":["england","flag_gbeng"]},{"emoji":"๐Ÿด๓ ง๓ ข๓ ณ๓ ฃ๓ ด๓ ฟ","group":9,"order":5041,"tags":["flag","gbsct"],"version":5,"annotation":"flag: Scotland","shortcodes":["flag_gbsct","scotland"]},{"emoji":"๐Ÿด๓ ง๓ ข๓ ท๓ ฌ๓ ณ๓ ฟ","group":9,"order":5042,"tags":["flag","gbwls"],"version":5,"annotation":"flag: Wales","shortcodes":["flag_gbwls","wales"]}] diff --git a/assets/ckeditor/html_label.js b/assets/ckeditor/html_label.js index 9040f3c77..72d1126e5 100644 --- a/assets/ckeditor/html_label.js +++ b/assets/ckeditor/html_label.js @@ -1,66 +1,63 @@ -/** - * @license Copyright (c) 2014-2022, CKSource Holding sp. z o.o. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license - */ -import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor.js'; -import Alignment from '@ckeditor/ckeditor5-alignment/src/alignment.js'; -import Autoformat from '@ckeditor/ckeditor5-autoformat/src/autoformat.js'; -import Base64UploadAdapter from '@ckeditor/ckeditor5-upload/src/adapters/base64uploadadapter.js'; -import BlockQuote from '@ckeditor/ckeditor5-block-quote/src/blockquote.js'; -import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold.js'; -import Code from '@ckeditor/ckeditor5-basic-styles/src/code.js'; -import CodeBlock from '@ckeditor/ckeditor5-code-block/src/codeblock.js'; -import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials.js'; -import FindAndReplace from '@ckeditor/ckeditor5-find-and-replace/src/findandreplace.js'; -import FontBackgroundColor from '@ckeditor/ckeditor5-font/src/fontbackgroundcolor.js'; -import FontColor from '@ckeditor/ckeditor5-font/src/fontcolor.js'; -import FontFamily from '@ckeditor/ckeditor5-font/src/fontfamily.js'; -import FontSize from '@ckeditor/ckeditor5-font/src/fontsize.js'; -import GeneralHtmlSupport from '@ckeditor/ckeditor5-html-support/src/generalhtmlsupport.js'; -import Heading from '@ckeditor/ckeditor5-heading/src/heading.js'; -import Highlight from '@ckeditor/ckeditor5-highlight/src/highlight.js'; -import HorizontalLine from '@ckeditor/ckeditor5-horizontal-line/src/horizontalline.js'; -import HtmlComment from '@ckeditor/ckeditor5-html-support/src/htmlcomment.js'; -import HtmlEmbed from '@ckeditor/ckeditor5-html-embed/src/htmlembed.js'; -import Image from '@ckeditor/ckeditor5-image/src/image.js'; -import ImageResize from '@ckeditor/ckeditor5-image/src/imageresize.js'; -import ImageStyle from '@ckeditor/ckeditor5-image/src/imagestyle.js'; -import ImageToolbar from '@ckeditor/ckeditor5-image/src/imagetoolbar.js'; -import ImageUpload from '@ckeditor/ckeditor5-image/src/imageupload.js'; -import Indent from '@ckeditor/ckeditor5-indent/src/indent.js'; -import IndentBlock from '@ckeditor/ckeditor5-indent/src/indentblock.js'; -import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic.js'; -import Link from '@ckeditor/ckeditor5-link/src/link.js'; -import LinkImage from '@ckeditor/ckeditor5-link/src/linkimage.js'; -import List from '@ckeditor/ckeditor5-list/src/list.js'; -import ListProperties from '@ckeditor/ckeditor5-list/src/listproperties.js'; -import Markdown from '@ckeditor/ckeditor5-markdown-gfm/src/markdown.js'; -import MediaEmbed from '@ckeditor/ckeditor5-media-embed/src/mediaembed.js'; -import MediaEmbedToolbar from '@ckeditor/ckeditor5-media-embed/src/mediaembedtoolbar.js'; -import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph.js'; -import PasteFromOffice from '@ckeditor/ckeditor5-paste-from-office/src/pastefromoffice.js'; -import RemoveFormat from '@ckeditor/ckeditor5-remove-format/src/removeformat.js'; -import SourceEditing from '@ckeditor/ckeditor5-source-editing/src/sourceediting.js'; -import SpecialCharacters from '@ckeditor/ckeditor5-special-characters/src/specialcharacters.js'; -import SpecialCharactersArrows from '@ckeditor/ckeditor5-special-characters/src/specialcharactersarrows.js'; -import SpecialCharactersCurrency from '@ckeditor/ckeditor5-special-characters/src/specialcharacterscurrency.js'; -import SpecialCharactersEssentials from '@ckeditor/ckeditor5-special-characters/src/specialcharactersessentials.js'; -import SpecialCharactersLatin from '@ckeditor/ckeditor5-special-characters/src/specialcharacterslatin.js'; -import SpecialCharactersMathematical from '@ckeditor/ckeditor5-special-characters/src/specialcharactersmathematical.js'; -import SpecialCharactersText from '@ckeditor/ckeditor5-special-characters/src/specialcharacterstext.js'; -import Strikethrough from '@ckeditor/ckeditor5-basic-styles/src/strikethrough.js'; -import Subscript from '@ckeditor/ckeditor5-basic-styles/src/subscript.js'; -import Superscript from '@ckeditor/ckeditor5-basic-styles/src/superscript.js'; -import Table from '@ckeditor/ckeditor5-table/src/table.js'; -import TableCaption from '@ckeditor/ckeditor5-table/src/tablecaption.js'; -import TableCellProperties from '@ckeditor/ckeditor5-table/src/tablecellproperties'; -import TableColumnResize from '@ckeditor/ckeditor5-table/src/tablecolumnresize.js'; -import TableProperties from '@ckeditor/ckeditor5-table/src/tableproperties'; -import TableToolbar from '@ckeditor/ckeditor5-table/src/tabletoolbar.js'; -import Underline from '@ckeditor/ckeditor5-basic-styles/src/underline.js'; -import WordCount from '@ckeditor/ckeditor5-word-count/src/wordcount.js'; -import EditorWatchdog from '@ckeditor/ckeditor5-watchdog/src/editorwatchdog.js'; +import {ClassicEditor} from 'ckeditor5' +import {Alignment} from 'ckeditor5'; +import {Autoformat} from 'ckeditor5'; +import {Base64UploadAdapter} from 'ckeditor5'; +import {BlockQuote} from 'ckeditor5'; +import {Bold} from 'ckeditor5'; +import {Code} from 'ckeditor5'; +import {CodeBlock} from 'ckeditor5'; +import {Essentials} from 'ckeditor5'; +import {FindAndReplace} from 'ckeditor5'; +import {FontBackgroundColor} from 'ckeditor5'; +import {FontColor} from 'ckeditor5'; +import {FontFamily} from 'ckeditor5'; +import {FontSize} from 'ckeditor5'; +import {GeneralHtmlSupport} from 'ckeditor5'; +import {Heading} from 'ckeditor5'; +import {Highlight} from 'ckeditor5'; +import {HorizontalLine} from 'ckeditor5'; +import {HtmlComment} from 'ckeditor5'; +import {HtmlEmbed} from 'ckeditor5'; +import {Image} from 'ckeditor5'; +import {ImageResize} from 'ckeditor5'; +import {ImageStyle} from 'ckeditor5'; +import {ImageToolbar} from 'ckeditor5'; +import {ImageUpload} from 'ckeditor5'; +import {Indent} from 'ckeditor5'; +import {IndentBlock} from 'ckeditor5'; +import {Italic} from 'ckeditor5'; +import {Link} from 'ckeditor5'; +import {LinkImage} from 'ckeditor5'; +import {List} from 'ckeditor5'; +import {ListProperties} from 'ckeditor5'; +import {Markdown} from 'ckeditor5'; +import {MediaEmbed} from 'ckeditor5'; +import {MediaEmbedToolbar} from 'ckeditor5'; +import {Paragraph} from 'ckeditor5'; +import {PasteFromOffice} from 'ckeditor5'; +import {RemoveFormat} from 'ckeditor5'; +import {SourceEditing} from 'ckeditor5'; +import {SpecialCharacters} from 'ckeditor5'; +import {SpecialCharactersArrows} from 'ckeditor5'; +import {SpecialCharactersCurrency} from 'ckeditor5'; +import {SpecialCharactersEssentials} from 'ckeditor5'; +import {SpecialCharactersLatin} from 'ckeditor5'; +import {SpecialCharactersMathematical} from 'ckeditor5'; +import {SpecialCharactersText} from 'ckeditor5'; +import {Strikethrough} from 'ckeditor5'; +import {Subscript} from 'ckeditor5'; +import {Superscript} from 'ckeditor5'; +import {Table} from 'ckeditor5'; +import {TableCaption} from 'ckeditor5'; +import {TableCellProperties} from 'ckeditor5'; +import {TableColumnResize} from 'ckeditor5'; +import {TableProperties} from 'ckeditor5'; +import {TableToolbar} from 'ckeditor5'; +import {Underline} from 'ckeditor5'; +import {WordCount} from 'ckeditor5'; +import {EditorWatchdog} from 'ckeditor5'; import PartDBLabel from "./plugins/PartDBLabel/PartDBLabel"; +import SpecialCharactersGreek from "./plugins/special_characters_emoji"; class Editor extends ClassicEditor {} @@ -122,7 +119,8 @@ Editor.builtinPlugins = [ Underline, WordCount, - PartDBLabel + PartDBLabel, + SpecialCharactersGreek ]; // Editor configuration. diff --git a/assets/ckeditor/markdown_full.js b/assets/ckeditor/markdown_full.js index 784bd6880..76944b86b 100644 --- a/assets/ckeditor/markdown_full.js +++ b/assets/ckeditor/markdown_full.js @@ -2,68 +2,69 @@ * @license Copyright (c) 2014-2022, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ -import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor.js'; -import Alignment from '@ckeditor/ckeditor5-alignment/src/alignment.js'; -import Autoformat from '@ckeditor/ckeditor5-autoformat/src/autoformat.js'; -import Base64UploadAdapter from '@ckeditor/ckeditor5-upload/src/adapters/base64uploadadapter.js'; -import BlockQuote from '@ckeditor/ckeditor5-block-quote/src/blockquote.js'; -import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold.js'; -import Code from '@ckeditor/ckeditor5-basic-styles/src/code.js'; -import CodeBlock from '@ckeditor/ckeditor5-code-block/src/codeblock.js'; -import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials.js'; -import FindAndReplace from '@ckeditor/ckeditor5-find-and-replace/src/findandreplace.js'; -import FontBackgroundColor from '@ckeditor/ckeditor5-font/src/fontbackgroundcolor.js'; -import FontColor from '@ckeditor/ckeditor5-font/src/fontcolor.js'; -import FontFamily from '@ckeditor/ckeditor5-font/src/fontfamily.js'; -import FontSize from '@ckeditor/ckeditor5-font/src/fontsize.js'; -import GeneralHtmlSupport from '@ckeditor/ckeditor5-html-support/src/generalhtmlsupport.js'; -import Heading from '@ckeditor/ckeditor5-heading/src/heading.js'; -import Highlight from '@ckeditor/ckeditor5-highlight/src/highlight.js'; -import HorizontalLine from '@ckeditor/ckeditor5-horizontal-line/src/horizontalline.js'; -import HtmlComment from '@ckeditor/ckeditor5-html-support/src/htmlcomment.js'; -import HtmlEmbed from '@ckeditor/ckeditor5-html-embed/src/htmlembed.js'; -import Image from '@ckeditor/ckeditor5-image/src/image.js'; -import ImageResize from '@ckeditor/ckeditor5-image/src/imageresize.js'; -import ImageStyle from '@ckeditor/ckeditor5-image/src/imagestyle.js'; -import ImageToolbar from '@ckeditor/ckeditor5-image/src/imagetoolbar.js'; -import ImageUpload from '@ckeditor/ckeditor5-image/src/imageupload.js'; -import Indent from '@ckeditor/ckeditor5-indent/src/indent.js'; -import IndentBlock from '@ckeditor/ckeditor5-indent/src/indentblock.js'; -import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic.js'; -import Link from '@ckeditor/ckeditor5-link/src/link.js'; -import LinkImage from '@ckeditor/ckeditor5-link/src/linkimage.js'; -import List from '@ckeditor/ckeditor5-list/src/list.js'; -import ListProperties from '@ckeditor/ckeditor5-list/src/listproperties.js'; -import Markdown from '@ckeditor/ckeditor5-markdown-gfm/src/markdown.js'; -import MediaEmbed from '@ckeditor/ckeditor5-media-embed/src/mediaembed.js'; -import MediaEmbedToolbar from '@ckeditor/ckeditor5-media-embed/src/mediaembedtoolbar.js'; -import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph.js'; -import PasteFromOffice from '@ckeditor/ckeditor5-paste-from-office/src/pastefromoffice.js'; -import RemoveFormat from '@ckeditor/ckeditor5-remove-format/src/removeformat.js'; -import SourceEditing from '@ckeditor/ckeditor5-source-editing/src/sourceediting.js'; -import SpecialCharacters from '@ckeditor/ckeditor5-special-characters/src/specialcharacters.js'; -import SpecialCharactersArrows from '@ckeditor/ckeditor5-special-characters/src/specialcharactersarrows.js'; -import SpecialCharactersCurrency from '@ckeditor/ckeditor5-special-characters/src/specialcharacterscurrency.js'; -import SpecialCharactersEssentials from '@ckeditor/ckeditor5-special-characters/src/specialcharactersessentials.js'; -import SpecialCharactersLatin from '@ckeditor/ckeditor5-special-characters/src/specialcharacterslatin.js'; -import SpecialCharactersMathematical from '@ckeditor/ckeditor5-special-characters/src/specialcharactersmathematical.js'; -import SpecialCharactersText from '@ckeditor/ckeditor5-special-characters/src/specialcharacterstext.js'; -import Strikethrough from '@ckeditor/ckeditor5-basic-styles/src/strikethrough.js'; -import Subscript from '@ckeditor/ckeditor5-basic-styles/src/subscript.js'; -import Superscript from '@ckeditor/ckeditor5-basic-styles/src/superscript.js'; -import Table from '@ckeditor/ckeditor5-table/src/table.js'; -import TableCaption from '@ckeditor/ckeditor5-table/src/tablecaption.js'; -import TableCellProperties from '@ckeditor/ckeditor5-table/src/tablecellproperties'; -import TableColumnResize from '@ckeditor/ckeditor5-table/src/tablecolumnresize.js'; -import TableProperties from '@ckeditor/ckeditor5-table/src/tableproperties'; -import TableToolbar from '@ckeditor/ckeditor5-table/src/tabletoolbar.js'; -import Underline from '@ckeditor/ckeditor5-basic-styles/src/underline.js'; -import WordCount from '@ckeditor/ckeditor5-word-count/src/wordcount.js'; -import EditorWatchdog from '@ckeditor/ckeditor5-watchdog/src/editorwatchdog.js'; -import TodoList from '@ckeditor/ckeditor5-list/src/todolist'; +import {ClassicEditor} from 'ckeditor5'; +import {Alignment} from 'ckeditor5'; +import {Autoformat} from 'ckeditor5'; +import {Base64UploadAdapter} from 'ckeditor5'; +import {BlockQuote} from 'ckeditor5'; +import {Bold} from 'ckeditor5'; +import {Code} from 'ckeditor5'; +import {CodeBlock} from 'ckeditor5'; +import {Essentials} from 'ckeditor5'; +import {FindAndReplace} from 'ckeditor5'; +import {FontBackgroundColor} from 'ckeditor5'; +import {FontColor} from 'ckeditor5'; +import {FontFamily} from 'ckeditor5'; +import {FontSize} from 'ckeditor5'; +import {GeneralHtmlSupport} from 'ckeditor5'; +import {Heading} from 'ckeditor5'; +import {Highlight} from 'ckeditor5'; +import {HorizontalLine} from 'ckeditor5'; +import {HtmlComment} from 'ckeditor5'; +import {HtmlEmbed} from 'ckeditor5'; +import {Image} from 'ckeditor5'; +import {ImageResize} from 'ckeditor5'; +import {ImageStyle} from 'ckeditor5'; +import {ImageToolbar} from 'ckeditor5'; +import {ImageUpload} from 'ckeditor5'; +import {Indent} from 'ckeditor5'; +import {IndentBlock} from 'ckeditor5'; +import {Italic} from 'ckeditor5'; +import {Link} from 'ckeditor5'; +import {LinkImage} from 'ckeditor5'; +import {List} from 'ckeditor5'; +import {ListProperties} from 'ckeditor5'; +import {Markdown} from 'ckeditor5'; +import {MediaEmbed} from 'ckeditor5'; +import {MediaEmbedToolbar} from 'ckeditor5'; +import {Paragraph} from 'ckeditor5'; +import {PasteFromOffice} from 'ckeditor5'; +import {RemoveFormat} from 'ckeditor5'; +import {SourceEditing} from 'ckeditor5'; +import {SpecialCharacters} from 'ckeditor5'; +import {SpecialCharactersArrows} from 'ckeditor5'; +import {SpecialCharactersCurrency} from 'ckeditor5'; +import {SpecialCharactersEssentials} from 'ckeditor5'; +import {SpecialCharactersLatin} from 'ckeditor5'; +import {SpecialCharactersMathematical} from 'ckeditor5'; +import {SpecialCharactersText} from 'ckeditor5'; +import {Strikethrough} from 'ckeditor5'; +import {Subscript} from 'ckeditor5'; +import {Superscript} from 'ckeditor5'; +import {Table} from 'ckeditor5'; +import {TableCaption} from 'ckeditor5'; +import {TableCellProperties} from 'ckeditor5'; +import {TableColumnResize} from 'ckeditor5'; +import {TableProperties} from 'ckeditor5'; +import {TableToolbar} from 'ckeditor5'; +import {Underline} from 'ckeditor5'; +import {WordCount} from 'ckeditor5'; +import {EditorWatchdog} from 'ckeditor5'; +import {TodoList} from 'ckeditor5'; import ExtendedMarkdown from "./plugins/extendedMarkdown.js"; -import SpecialCharactersEmoji from "./plugins/special_characters_emoji"; +import SpecialCharactersGreek from "./plugins/special_characters_emoji"; +import {Mention, Emoji} from "ckeditor5"; class Editor extends ClassicEditor {} @@ -117,9 +118,11 @@ Editor.builtinPlugins = [ Underline, TodoList, + Mention, Emoji, + //Our own extensions ExtendedMarkdown, - SpecialCharactersEmoji + SpecialCharactersGreek ]; // Editor configuration. @@ -148,6 +151,7 @@ Editor.defaultConfig = { 'indent', '|', 'specialCharacters', + "emoji", 'horizontalLine', '|', 'imageUpload', diff --git a/assets/ckeditor/markdown_single_line.js b/assets/ckeditor/markdown_single_line.js index f7e91aa92..f05983a26 100644 --- a/assets/ckeditor/markdown_single_line.js +++ b/assets/ckeditor/markdown_single_line.js @@ -2,35 +2,36 @@ * @license Copyright (c) 2014-2022, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ -import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor.js'; -import Autoformat from '@ckeditor/ckeditor5-autoformat/src/autoformat.js'; -import AutoLink from '@ckeditor/ckeditor5-link/src/autolink.js'; -import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold.js'; -import Code from '@ckeditor/ckeditor5-basic-styles/src/code.js'; -import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials.js'; -import FindAndReplace from '@ckeditor/ckeditor5-find-and-replace/src/findandreplace.js'; -import Highlight from '@ckeditor/ckeditor5-highlight/src/highlight.js'; -import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic.js'; -import Link from '@ckeditor/ckeditor5-link/src/link.js'; -import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph.js'; -import RemoveFormat from '@ckeditor/ckeditor5-remove-format/src/removeformat.js'; -import SourceEditing from '@ckeditor/ckeditor5-source-editing/src/sourceediting.js'; -import SpecialCharacters from '@ckeditor/ckeditor5-special-characters/src/specialcharacters.js'; -import SpecialCharactersArrows from '@ckeditor/ckeditor5-special-characters/src/specialcharactersarrows.js'; -import SpecialCharactersCurrency from '@ckeditor/ckeditor5-special-characters/src/specialcharacterscurrency.js'; -import SpecialCharactersEssentials from '@ckeditor/ckeditor5-special-characters/src/specialcharactersessentials.js'; -import SpecialCharactersLatin from '@ckeditor/ckeditor5-special-characters/src/specialcharacterslatin.js'; -import SpecialCharactersMathematical from '@ckeditor/ckeditor5-special-characters/src/specialcharactersmathematical.js'; -import SpecialCharactersText from '@ckeditor/ckeditor5-special-characters/src/specialcharacterstext.js'; -import Strikethrough from '@ckeditor/ckeditor5-basic-styles/src/strikethrough.js'; -import Subscript from '@ckeditor/ckeditor5-basic-styles/src/subscript.js'; -import Superscript from '@ckeditor/ckeditor5-basic-styles/src/superscript.js'; -import Underline from '@ckeditor/ckeditor5-basic-styles/src/underline.js'; -import EditorWatchdog from '@ckeditor/ckeditor5-watchdog/src/editorwatchdog.js'; +import {ClassicEditor} from 'ckeditor5'; +import {Autoformat} from 'ckeditor5'; +import {AutoLink} from 'ckeditor5'; +import {Bold} from 'ckeditor5'; +import {Code} from 'ckeditor5'; +import {Essentials} from 'ckeditor5'; +import {FindAndReplace} from 'ckeditor5'; +import {Highlight} from 'ckeditor5'; +import {Italic} from 'ckeditor5'; +import {Link} from 'ckeditor5'; +import {Paragraph} from 'ckeditor5'; +import {RemoveFormat} from 'ckeditor5'; +import {SourceEditing} from 'ckeditor5'; +import {SpecialCharacters} from 'ckeditor5'; +import {SpecialCharactersArrows} from 'ckeditor5'; +import {SpecialCharactersCurrency} from 'ckeditor5'; +import {SpecialCharactersEssentials} from 'ckeditor5'; +import {SpecialCharactersLatin} from 'ckeditor5'; +import {SpecialCharactersMathematical} from 'ckeditor5'; +import {SpecialCharactersText} from 'ckeditor5'; +import {Strikethrough} from 'ckeditor5'; +import {Subscript} from 'ckeditor5'; +import {Superscript} from 'ckeditor5'; +import {Underline} from 'ckeditor5'; +import {EditorWatchdog} from 'ckeditor5'; +import {Mention, Emoji} from "ckeditor5"; import ExtendedMarkdownInline from "./plugins/extendedMarkdownInline"; import SingleLinePlugin from "./plugins/singleLine"; -import SpecialCharactersEmoji from "./plugins/special_characters_emoji"; +import SpecialCharactersGreek from "./plugins/special_characters_emoji"; class Editor extends ClassicEditor {} @@ -62,7 +63,8 @@ Editor.builtinPlugins = [ ExtendedMarkdownInline, SingleLinePlugin, - SpecialCharactersEmoji + SpecialCharactersGreek, + Mention, Emoji ]; // Editor configuration. @@ -81,6 +83,7 @@ Editor.defaultConfig = { 'link', 'code', 'specialCharacters', + 'emoji', '|', 'undo', 'redo', diff --git a/assets/ckeditor/plugins/PartDBLabel/PartDBLabel.js b/assets/ckeditor/plugins/PartDBLabel/PartDBLabel.js index 01e1c7bf4..708d4ebbb 100644 --- a/assets/ckeditor/plugins/PartDBLabel/PartDBLabel.js +++ b/assets/ckeditor/plugins/PartDBLabel/PartDBLabel.js @@ -22,7 +22,7 @@ import PartDBLabelEditing from "./PartDBLabelEditing"; import "./PartDBLabel.css"; -import Plugin from "@ckeditor/ckeditor5-core/src/plugin"; +import {Plugin} from "ckeditor5"; export default class PartDBLabel extends Plugin { static get requires() { @@ -32,4 +32,4 @@ export default class PartDBLabel extends Plugin { static get pluginName() { return 'PartDBLabel'; } -} \ No newline at end of file +} diff --git a/assets/ckeditor/plugins/PartDBLabel/PartDBLabelCommand.js b/assets/ckeditor/plugins/PartDBLabel/PartDBLabelCommand.js index 4c3af3ef6..7b9797e73 100644 --- a/assets/ckeditor/plugins/PartDBLabel/PartDBLabelCommand.js +++ b/assets/ckeditor/plugins/PartDBLabel/PartDBLabelCommand.js @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -import Command from '@ckeditor/ckeditor5-core/src/command'; +import {Command} from 'ckeditor5'; export default class PartDBLabelCommand extends Command { execute( { value } ) { @@ -47,4 +47,4 @@ export default class PartDBLabelCommand extends Command { this.isEnabled = isAllowed; } -} \ No newline at end of file +} diff --git a/assets/ckeditor/plugins/PartDBLabel/PartDBLabelEditing.js b/assets/ckeditor/plugins/PartDBLabel/PartDBLabelEditing.js index e61fb8955..5cb4860f6 100644 --- a/assets/ckeditor/plugins/PartDBLabel/PartDBLabelEditing.js +++ b/assets/ckeditor/plugins/PartDBLabel/PartDBLabelEditing.js @@ -17,11 +17,11 @@ * along with this program. If not, see . */ -import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; +import {Plugin} from 'ckeditor5'; import PartDBLabelCommand from "./PartDBLabelCommand"; -import { toWidget } from '@ckeditor/ckeditor5-widget/src/utils'; -import Widget from '@ckeditor/ckeditor5-widget/src/widget'; +import { toWidget } from 'ckeditor5'; +import {Widget} from 'ckeditor5'; export default class PartDBLabelEditing extends Plugin { static get requires() { // ADDED @@ -102,4 +102,4 @@ export default class PartDBLabelEditing extends Plugin { } } -} \ No newline at end of file +} diff --git a/assets/ckeditor/plugins/PartDBLabel/PartDBLabelUI.js b/assets/ckeditor/plugins/PartDBLabel/PartDBLabelUI.js index aa29e889d..bb9fcd1f9 100644 --- a/assets/ckeditor/plugins/PartDBLabel/PartDBLabelUI.js +++ b/assets/ckeditor/plugins/PartDBLabel/PartDBLabelUI.js @@ -17,14 +17,15 @@ * along with this program. If not, see . */ -import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; +import {Plugin} from 'ckeditor5'; require('./lang/de.js'); +require('./lang/en.js'); -import { addListToDropdown, createDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils'; +import { addListToDropdown, createDropdown } from 'ckeditor5'; -import Collection from '@ckeditor/ckeditor5-utils/src/collection'; -import Model from '@ckeditor/ckeditor5-ui/src/model'; +import {Collection} from 'ckeditor5'; +import {UIModel} from 'ckeditor5'; export default class PartDBLabelUI extends Plugin { init() { @@ -128,6 +129,8 @@ const PLACEHOLDERS = [ ['[[BARCODE_QR]]', 'QR code linking to this element'], ['[[BARCODE_C128]]', 'Code 128 barcode linking to this element'], ['[[BARCODE_C39]]', 'Code 39 barcode linking to this element'], + ['[[BARCODE_C93]]', 'Code 93 barcode linking to this element'], + ['[[BARCODE_DATAMATRIX]]', 'Datamatrix code linking to this element'], ] }, { @@ -149,18 +152,28 @@ const PLACEHOLDERS = [ function getDropdownItemsDefinitions(t) { const itemDefinitions = new Collection(); + let first = true; + for ( const group of PLACEHOLDERS) { + //Add group header - itemDefinitions.add({ - 'type': 'separator', - model: new Model( { - withText: true, - }) - }); + + //Skip separator for first group + if (!first) { + + itemDefinitions.add({ + 'type': 'separator', + model: new UIModel( { + withText: true, + }) + }); + } else { + first = false; + } itemDefinitions.add({ type: 'button', - model: new Model( { + model: new UIModel( { label: t(group.label), withText: true, isEnabled: false, @@ -171,7 +184,7 @@ function getDropdownItemsDefinitions(t) { for ( const entry of group.entries) { const definition = { type: 'button', - model: new Model( { + model: new UIModel( { commandParam: entry[0], label: t(entry[1]), tooltip: entry[0], @@ -185,4 +198,4 @@ function getDropdownItemsDefinitions(t) { } return itemDefinitions; -} \ No newline at end of file +} diff --git a/assets/ckeditor/plugins/PartDBLabel/lang/de.js b/assets/ckeditor/plugins/PartDBLabel/lang/de.js index 2220cc0b6..e0ca05217 100644 --- a/assets/ckeditor/plugins/PartDBLabel/lang/de.js +++ b/assets/ckeditor/plugins/PartDBLabel/lang/de.js @@ -17,15 +17,9 @@ * along with this program. If not, see . */ -// Make sure that the global object is defined. If not, define it. -window.CKEDITOR_TRANSLATIONS = window.CKEDITOR_TRANSLATIONS || {}; +import {add} from "ckeditor5"; -// Make sure that the dictionary for Polish translations exist. -window.CKEDITOR_TRANSLATIONS[ 'de' ] = window.CKEDITOR_TRANSLATIONS[ 'de' ] || {}; -window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary = window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary || {}; - -// Extend the dictionary for Polish translations with your translations: -Object.assign( window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary, { +add( "de", { 'Label Placeholder': 'Label Platzhalter', 'Part': 'Bauteil', @@ -69,6 +63,8 @@ Object.assign( window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary, { 'QR code linking to this element': 'QR Code verknรผpft mit diesem Element', 'Code 128 barcode linking to this element': 'Code 128 Barcode verknรผpft mit diesem Element', 'Code 39 barcode linking to this element': 'Code 39 Barcode verknรผpft mit diesem Element', + 'Code 93 barcode linking to this element': 'Code 93 Barcode verknรผpft mit diesem Element', + 'Datamatrix code linking to this element': 'Datamatrix Code verknรผpft mit diesem Element', 'Location ID': 'Lagerort ID', 'Name': 'Name', @@ -86,5 +82,4 @@ Object.assign( window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary, { 'Instance name': 'Instanzname', 'Target type': 'Zieltyp', 'URL of this Part-DB instance': 'URL dieser Part-DB Instanz', - -} ); \ No newline at end of file +}); diff --git a/assets/ckeditor/plugins/PartDBLabel/lang/en.js b/assets/ckeditor/plugins/PartDBLabel/lang/en.js new file mode 100644 index 000000000..8f77aaf15 --- /dev/null +++ b/assets/ckeditor/plugins/PartDBLabel/lang/en.js @@ -0,0 +1,84 @@ +/* + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2025 Jan Bรถhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import {add} from "ckeditor5"; + +add( "en", { + 'Label Placeholder': 'Label placeholder', + 'Part': 'Part', + + 'Database ID': 'Database ID', + 'Part name': 'Part name', + 'Category': 'Category', + 'Category (Full path)': 'Category (full path)', + 'Manufacturer': 'Manufacturer', + 'Manufacturer (Full path)': 'Manufacturer (full path)', + 'Footprint': 'Footprint', + 'Footprint (Full path)': 'Footprint (full path)', + 'Mass': 'Mass', + 'Manufacturer Product Number (MPN)': 'Manufacturer Product Number (MPN)', + 'Internal Part Number (IPN)': 'Internal Part Number (IPN)', + 'Tags': 'Tags', + 'Manufacturing status': 'Manufacturing status', + 'Description': 'Description', + 'Description (plain text)': 'Description (plain text)', + 'Comment': 'Comment', + 'Comment (plain text)': 'Comment (plain text)', + 'Last modified datetime': 'Last modified datetime', + 'Creation datetime': 'Creation datetime', + 'IPN as QR code': 'IPN as QR code', + 'IPN as Code 128 barcode': 'IPN as Code 128 barcode', + 'IPN as Code 39 barcode': 'IPN as Code 39 barcode', + + 'Lot ID': 'Lot ID', + 'Lot name': 'Lot name', + 'Lot comment': 'Lot comment', + 'Lot expiration date': 'Lot expiration date', + 'Lot amount': 'Lot amount', + 'Storage location': 'Storage location', + 'Storage location (Full path)': 'Storage location (full path)', + 'Full name of the lot owner': 'Full name of the lot owner', + 'Username of the lot owner': 'Username of the lot owner', + + 'Barcodes': 'Barcodes', + 'Content of the 1D barcodes (like Code 39)': 'Content of the 1D barcodes (like Code 39)', + 'Content of the 2D barcodes (QR codes)': 'Content of the 2D barcodes (QR codes)', + 'QR code linking to this element': 'QR code linking to this element', + 'Code 128 barcode linking to this element': 'Code 128 barcode linking to this element', + 'Code 39 barcode linking to this element': 'Code 39 barcode linking to this element', + 'Code 93 barcode linking to this element': 'Code 93 barcode linking to this element', + 'Datamatrix code linking to this element': 'Datamatrix code linking to this element', + + 'Location ID': 'Location ID', + 'Name': 'Name', + 'Full path': 'Full path', + 'Parent name': 'Parent name', + 'Parent full path': 'Parent full path', + 'Full name of the location owner': 'Full name of the location owner', + 'Username of the location owner': 'Username of the location owner', + + 'Username': 'Username', + 'Username (including name)': 'Username (including name)', + 'Current datetime': 'Current datetime', + 'Current date': 'Current date', + 'Current time': 'Current time', + 'Instance name': 'Instance name', + 'Target type': 'Target type', + 'URL of this Part-DB instance': 'URL of this Part-DB instance', +} ); diff --git a/assets/ckeditor/plugins/extendedMarkdown.js b/assets/ckeditor/plugins/extendedMarkdown.js index 987388cd4..fc861175e 100644 --- a/assets/ckeditor/plugins/extendedMarkdown.js +++ b/assets/ckeditor/plugins/extendedMarkdown.js @@ -17,8 +17,7 @@ * along with this program. If not, see . */ -import { Plugin } from 'ckeditor5/src/core'; -import GFMDataProcessor from '@ckeditor/ckeditor5-markdown-gfm/src/gfmdataprocessor'; +import { Plugin, MarkdownGfmDataProcessor } from 'ckeditor5'; const ALLOWED_TAGS = [ //Common elements @@ -34,7 +33,6 @@ const ALLOWED_TAGS = [ //Block elements 'span', - 'p', 'img', @@ -57,7 +55,7 @@ export default class ExtendedMarkdown extends Plugin { constructor( editor ) { super( editor ); - editor.data.processor = new GFMDataProcessor( editor.data.viewDocument ); + editor.data.processor = new MarkdownGfmDataProcessor( editor.data.viewDocument ); for (const tag of ALLOWED_TAGS) { editor.data.processor.keepHtml(tag); } diff --git a/assets/ckeditor/plugins/extendedMarkdownInline.js b/assets/ckeditor/plugins/extendedMarkdownInline.js index 21d4074c1..695e70895 100644 --- a/assets/ckeditor/plugins/extendedMarkdownInline.js +++ b/assets/ckeditor/plugins/extendedMarkdownInline.js @@ -17,8 +17,8 @@ * along with this program. If not, see . */ -import { Plugin } from 'ckeditor5/src/core'; -import GFMDataProcessor from '@ckeditor/ckeditor5-markdown-gfm/src/gfmdataprocessor'; +import {Plugin} from 'ckeditor5'; +import {MarkdownGfmDataProcessor} from 'ckeditor5'; const ALLOWED_TAGS = [ //Common elements @@ -46,7 +46,7 @@ export default class ExtendedMarkdownInline extends Plugin { constructor( editor ) { super( editor ); - editor.data.processor = new GFMDataProcessor( editor.data.viewDocument ); + editor.data.processor = new MarkdownGfmDataProcessor( editor.data.viewDocument ); for (const tag of ALLOWED_TAGS) { editor.data.processor.keepHtml(tag); } diff --git a/assets/ckeditor/plugins/singleLine.js b/assets/ckeditor/plugins/singleLine.js index 79ac9c6fb..be84dd22c 100644 --- a/assets/ckeditor/plugins/singleLine.js +++ b/assets/ckeditor/plugins/singleLine.js @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; +import {Plugin} from 'ckeditor5'; export default class SingleLinePlugin extends Plugin { init() { @@ -42,7 +42,7 @@ export default class SingleLinePlugin extends Plugin { //We can not use the dataTransfer.setData method because the old object is somehow protected data.dataTransfer = new DataTransfer(); data.dataTransfer.setData("text", cleaned); - + }, { priority: 'high' } ); } -} \ No newline at end of file +} diff --git a/assets/ckeditor/plugins/special_characters_emoji.js b/assets/ckeditor/plugins/special_characters_emoji.js index 1d4ec000a..1a57def25 100644 --- a/assets/ckeditor/plugins/special_characters_emoji.js +++ b/assets/ckeditor/plugins/special_characters_emoji.js @@ -17,14 +17,12 @@ * along with this program. If not, see . */ -import SpecialCharacters from '@ckeditor/ckeditor5-special-characters/src/specialcharacters'; -import SpecialCharactersEssentials from '@ckeditor/ckeditor5-special-characters/src/specialcharactersessentials'; +import SpecialCharacters from 'ckeditor5'; +import SpecialCharactersEssentials from 'ckeditor5'; -import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; +import {Plugin} from 'ckeditor5'; -const emoji = require('emoji.json'); - -export default class SpecialCharactersEmoji extends Plugin { +export default class SpecialCharactersGreek extends Plugin { init() { const editor = this.editor; @@ -32,9 +30,6 @@ export default class SpecialCharactersEmoji extends Plugin { //Add greek characters to special characters specialCharsPlugin.addItems('Greek', this.getGreek()); - - //Add Emojis to special characters - specialCharsPlugin.addItems('Emoji', this.getEmojis()); } getGreek() { @@ -96,14 +91,4 @@ export default class SpecialCharactersEmoji extends Plugin { { title: 'san', character: 'ฯบ' }, ]; } - - getEmojis() { - //Map our emoji data to the format the plugin expects - return emoji.map(emoji => { - return { - title: emoji.name, - character: emoji.char - }; - }); - } -} \ No newline at end of file +} diff --git a/assets/controllers/bulk_import_controller.js b/assets/controllers/bulk_import_controller.js new file mode 100644 index 000000000..49e4d60ff --- /dev/null +++ b/assets/controllers/bulk_import_controller.js @@ -0,0 +1,359 @@ +import { Controller } from "@hotwired/stimulus" +import { generateCsrfHeaders } from "./csrf_protection_controller" + +export default class extends Controller { + static targets = ["progressBar", "progressText"] + static values = { + jobId: Number, + partId: Number, + researchUrl: String, + researchAllUrl: String, + markCompletedUrl: String, + markSkippedUrl: String, + markPendingUrl: String + } + + connect() { + // Auto-refresh progress if job is in progress + if (this.hasProgressBarTarget) { + this.startProgressUpdates() + } + + // Restore scroll position after page reload (if any) + this.restoreScrollPosition() + } + + getHeaders() { + const headers = { + 'Content-Type': 'application/json', + 'X-Requested-With': 'XMLHttpRequest' + } + + // Add CSRF headers if available + const form = document.querySelector('form') + if (form) { + const csrfHeaders = generateCsrfHeaders(form) + Object.assign(headers, csrfHeaders) + } + + return headers + } + + async fetchWithErrorHandling(url, options = {}, timeout = 30000) { + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), timeout) + + try { + const response = await fetch(url, { + ...options, + headers: { ...this.getHeaders(), ...options.headers }, + signal: controller.signal + }) + + clearTimeout(timeoutId) + + if (!response.ok) { + const errorText = await response.text() + throw new Error(`Server error (${response.status}): ${errorText}`) + } + + return await response.json() + } catch (error) { + clearTimeout(timeoutId) + + if (error.name === 'AbortError') { + throw new Error('Request timed out. Please try again.') + } else if (error.message.includes('Failed to fetch')) { + throw new Error('Network error. Please check your connection and try again.') + } else { + throw error + } + } + } + + disconnect() { + if (this.progressInterval) { + clearInterval(this.progressInterval) + } + } + + startProgressUpdates() { + // Progress updates are handled via page reload for better reliability + // No need for periodic updates since state changes trigger page refresh + } + + restoreScrollPosition() { + const savedPosition = sessionStorage.getItem('bulkImportScrollPosition') + if (savedPosition) { + // Restore scroll position after a small delay to ensure page is fully loaded + setTimeout(() => { + window.scrollTo(0, parseInt(savedPosition)) + // Clear the saved position so it doesn't interfere with normal navigation + sessionStorage.removeItem('bulkImportScrollPosition') + }, 100) + } + } + + async markCompleted(event) { + const partId = event.currentTarget.dataset.partId + + try { + const url = this.markCompletedUrlValue.replace('__PART_ID__', partId) + const data = await this.fetchWithErrorHandling(url, { method: 'POST' }) + + if (data.success) { + this.updateProgressDisplay(data) + this.markRowAsCompleted(partId) + + if (data.job_completed) { + this.showJobCompletedMessage() + } + } else { + this.showErrorMessage(data.error || 'Failed to mark part as completed') + } + } catch (error) { + console.error('Error marking part as completed:', error) + this.showErrorMessage(error.message || 'Failed to mark part as completed') + } + } + + async markSkipped(event) { + const partId = event.currentTarget.dataset.partId + const reason = prompt('Reason for skipping (optional):') || '' + + try { + const url = this.markSkippedUrlValue.replace('__PART_ID__', partId) + const data = await this.fetchWithErrorHandling(url, { + method: 'POST', + body: JSON.stringify({ reason }) + }) + + if (data.success) { + this.updateProgressDisplay(data) + this.markRowAsSkipped(partId) + } else { + this.showErrorMessage(data.error || 'Failed to mark part as skipped') + } + } catch (error) { + console.error('Error marking part as skipped:', error) + this.showErrorMessage(error.message || 'Failed to mark part as skipped') + } + } + + async markPending(event) { + const partId = event.currentTarget.dataset.partId + + try { + const url = this.markPendingUrlValue.replace('__PART_ID__', partId) + const data = await this.fetchWithErrorHandling(url, { method: 'POST' }) + + if (data.success) { + this.updateProgressDisplay(data) + this.markRowAsPending(partId) + } else { + this.showErrorMessage(data.error || 'Failed to mark part as pending') + } + } catch (error) { + console.error('Error marking part as pending:', error) + this.showErrorMessage(error.message || 'Failed to mark part as pending') + } + } + + updateProgressDisplay(data) { + if (this.hasProgressBarTarget) { + this.progressBarTarget.style.width = `${data.progress}%` + this.progressBarTarget.setAttribute('aria-valuenow', data.progress) + } + + if (this.hasProgressTextTarget) { + this.progressTextTarget.textContent = `${data.completed_count} / ${data.total_count} completed` + } + } + + markRowAsCompleted(partId) { + // Save scroll position and refresh page to show updated state + sessionStorage.setItem('bulkImportScrollPosition', window.scrollY.toString()) + window.location.reload() + } + + markRowAsSkipped(partId) { + // Save scroll position and refresh page to show updated state + sessionStorage.setItem('bulkImportScrollPosition', window.scrollY.toString()) + window.location.reload() + } + + markRowAsPending(partId) { + // Save scroll position and refresh page to show updated state + sessionStorage.setItem('bulkImportScrollPosition', window.scrollY.toString()) + window.location.reload() + } + + showJobCompletedMessage() { + const alert = document.createElement('div') + alert.className = 'alert alert-success alert-dismissible fade show' + alert.innerHTML = ` + + Job completed! All parts have been processed. + + ` + + const container = document.querySelector('.card-body') + container.insertBefore(alert, container.firstChild) + } + + async researchPart(event) { + event.preventDefault() + event.stopPropagation() + + const partId = event.currentTarget.dataset.partId + const spinner = event.currentTarget.querySelector(`[data-research-spinner="${partId}"]`) + const button = event.currentTarget + + // Show loading state + if (spinner) { + spinner.style.display = 'inline-block' + } + button.disabled = true + + try { + const url = this.researchUrlValue.replace('__PART_ID__', partId) + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), 30000) // 30 second timeout + + const response = await fetch(url, { + method: 'POST', + headers: this.getHeaders(), + signal: controller.signal + }) + + clearTimeout(timeoutId) + + if (!response.ok) { + const errorText = await response.text() + throw new Error(`Server error (${response.status}): ${errorText}`) + } + + const data = await response.json() + + if (data.success) { + this.showSuccessMessage(`Research completed for part. Found ${data.results_count} results.`) + // Save scroll position and reload to show updated results + sessionStorage.setItem('bulkImportScrollPosition', window.scrollY.toString()) + window.location.reload() + } else { + this.showErrorMessage(data.error || 'Research failed') + } + } catch (error) { + console.error('Error researching part:', error) + + if (error.name === 'AbortError') { + this.showErrorMessage('Research timed out. Please try again.') + } else if (error.message.includes('Failed to fetch')) { + this.showErrorMessage('Network error. Please check your connection and try again.') + } else { + this.showErrorMessage(error.message || 'Research failed due to an unexpected error') + } + } finally { + // Hide loading state + if (spinner) { + spinner.style.display = 'none' + } + button.disabled = false + } + } + + async researchAllParts(event) { + event.preventDefault() + event.stopPropagation() + + const spinner = document.getElementById('research-all-spinner') + const button = event.currentTarget + + // Show loading state + if (spinner) { + spinner.style.display = 'inline-block' + } + button.disabled = true + + try { + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), 120000) // 2 minute timeout for bulk operations + + const response = await fetch(this.researchAllUrlValue, { + method: 'POST', + headers: this.getHeaders(), + signal: controller.signal + }) + + clearTimeout(timeoutId) + + if (!response.ok) { + const errorText = await response.text() + throw new Error(`Server error (${response.status}): ${errorText}`) + } + + const data = await response.json() + + if (data.success) { + this.showSuccessMessage(`Research completed for ${data.researched_count} parts.`) + // Save scroll position and reload to show updated results + sessionStorage.setItem('bulkImportScrollPosition', window.scrollY.toString()) + window.location.reload() + } else { + this.showErrorMessage(data.error || 'Bulk research failed') + } + } catch (error) { + console.error('Error researching all parts:', error) + + if (error.name === 'AbortError') { + this.showErrorMessage('Bulk research timed out. This may happen with large batches. Please try again or process smaller batches.') + } else if (error.message.includes('Failed to fetch')) { + this.showErrorMessage('Network error. Please check your connection and try again.') + } else { + this.showErrorMessage(error.message || 'Bulk research failed due to an unexpected error') + } + } finally { + // Hide loading state + if (spinner) { + spinner.style.display = 'none' + } + button.disabled = false + } + } + + showSuccessMessage(message) { + this.showToast('success', message) + } + + showErrorMessage(message) { + this.showToast('error', message) + } + + showToast(type, message) { + // Create a simple alert that doesn't disrupt layout + const alertId = 'alert-' + Date.now() + const iconClass = type === 'success' ? 'fa-check-circle' : 'fa-exclamation-triangle' + const alertClass = type === 'success' ? 'alert-success' : 'alert-danger' + + const alertHTML = ` +
+ + ${message} + +
+ ` + + // Add alert to body + document.body.insertAdjacentHTML('beforeend', alertHTML) + + // Auto-remove after 5 seconds + setTimeout(() => { + const alertElement = document.getElementById(alertId) + if (alertElement) { + alertElement.remove() + } + }, 5000) + } +} \ No newline at end of file diff --git a/assets/controllers/bulk_job_manage_controller.js b/assets/controllers/bulk_job_manage_controller.js new file mode 100644 index 000000000..c26e37c61 --- /dev/null +++ b/assets/controllers/bulk_job_manage_controller.js @@ -0,0 +1,92 @@ +import { Controller } from "@hotwired/stimulus" +import { generateCsrfHeaders } from "./csrf_protection_controller" + +export default class extends Controller { + static values = { + deleteUrl: String, + stopUrl: String, + deleteConfirmMessage: String, + stopConfirmMessage: String + } + + connect() { + // Controller initialized + } + getHeaders() { + const headers = { + 'X-Requested-With': 'XMLHttpRequest' + } + + // Add CSRF headers if available + const form = document.querySelector('form') + if (form) { + const csrfHeaders = generateCsrfHeaders(form) + Object.assign(headers, csrfHeaders) + } + + return headers + } + async deleteJob(event) { + const jobId = event.currentTarget.dataset.jobId + const confirmMessage = this.deleteConfirmMessageValue || 'Are you sure you want to delete this job?' + + if (confirm(confirmMessage)) { + try { + const deleteUrl = this.deleteUrlValue.replace('__JOB_ID__', jobId) + + const response = await fetch(deleteUrl, { + method: 'DELETE', + headers: this.getHeaders() + }) + + if (!response.ok) { + const errorText = await response.text() + throw new Error(`HTTP ${response.status}: ${errorText}`) + } + + const data = await response.json() + + if (data.success) { + location.reload() + } else { + alert('Error deleting job: ' + (data.error || 'Unknown error')) + } + } catch (error) { + console.error('Error deleting job:', error) + alert('Error deleting job: ' + error.message) + } + } + } + + async stopJob(event) { + const jobId = event.currentTarget.dataset.jobId + const confirmMessage = this.stopConfirmMessageValue || 'Are you sure you want to stop this job?' + + if (confirm(confirmMessage)) { + try { + const stopUrl = this.stopUrlValue.replace('__JOB_ID__', jobId) + + const response = await fetch(stopUrl, { + method: 'POST', + headers: this.getHeaders() + }) + + if (!response.ok) { + const errorText = await response.text() + throw new Error(`HTTP ${response.status}: ${errorText}`) + } + + const data = await response.json() + + if (data.success) { + location.reload() + } else { + alert('Error stopping job: ' + (data.error || 'Unknown error')) + } + } catch (error) { + console.error('Error stopping job:', error) + alert('Error stopping job: ' + error.message) + } + } + } +} \ No newline at end of file diff --git a/assets/controllers/common/markdown_controller.js b/assets/controllers/common/markdown_controller.js index b6ef00344..c6cb97df2 100644 --- a/assets/controllers/common/markdown_controller.js +++ b/assets/controllers/common/markdown_controller.js @@ -56,12 +56,16 @@ export default class MarkdownController extends Controller { this.element.innerHTML = DOMPurify.sanitize(MarkdownController._marked.parse(this.unescapeHTML(raw))); for(let a of this.element.querySelectorAll('a')) { - //Mark all links as external - a.classList.add('link-external'); - //Open links in new tag - a.setAttribute('target', '_blank'); - //Dont track - a.setAttribute('rel', 'noopener'); + // test if link is absolute + var r = new RegExp('^(?:[a-z+]+:)?//', 'i'); + if (r.test(a.getAttribute('href'))) { + //Mark all links as external + a.classList.add('link-external'); + //Open links in new tag + a.setAttribute('target', '_blank'); + //Dont track + a.setAttribute('rel', 'noopener'); + } } //Apply bootstrap styles to tables @@ -108,4 +112,4 @@ export default class MarkdownController extends Controller { gfm: true, }); }*/ -} \ No newline at end of file +} diff --git a/assets/controllers/csrf_protection_controller.js b/assets/controllers/csrf_protection_controller.js new file mode 100644 index 000000000..c722f024a --- /dev/null +++ b/assets/controllers/csrf_protection_controller.js @@ -0,0 +1,79 @@ +const nameCheck = /^[-_a-zA-Z0-9]{4,22}$/; +const tokenCheck = /^[-_/+a-zA-Z0-9]{24,}$/; + +// Generate and double-submit a CSRF token in a form field and a cookie, as defined by Symfony's SameOriginCsrfTokenManager +document.addEventListener('submit', function (event) { + generateCsrfToken(event.target); +}, true); + +// When @hotwired/turbo handles form submissions, send the CSRF token in a header in addition to a cookie +// The `framework.csrf_protection.check_header` config option needs to be enabled for the header to be checked +document.addEventListener('turbo:submit-start', function (event) { + const h = generateCsrfHeaders(event.detail.formSubmission.formElement); + Object.keys(h).map(function (k) { + event.detail.formSubmission.fetchRequest.headers[k] = h[k]; + }); +}); + +// When @hotwired/turbo handles form submissions, remove the CSRF cookie once a form has been submitted +document.addEventListener('turbo:submit-end', function (event) { + removeCsrfToken(event.detail.formSubmission.formElement); +}); + +export function generateCsrfToken (formElement) { + const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]'); + + if (!csrfField) { + return; + } + + let csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value'); + let csrfToken = csrfField.value; + + if (!csrfCookie && nameCheck.test(csrfToken)) { + csrfField.setAttribute('data-csrf-protection-cookie-value', csrfCookie = csrfToken); + csrfField.defaultValue = csrfToken = btoa(String.fromCharCode.apply(null, (window.crypto || window.msCrypto).getRandomValues(new Uint8Array(18)))); + csrfField.dispatchEvent(new Event('change', { bubbles: true })); + } + + if (csrfCookie && tokenCheck.test(csrfToken)) { + const cookie = csrfCookie + '_' + csrfToken + '=' + csrfCookie + '; path=/; samesite=strict'; + document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie; + } +} + +export function generateCsrfHeaders (formElement) { + const headers = {}; + const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]'); + + if (!csrfField) { + return headers; + } + + const csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value'); + + if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) { + headers[csrfCookie] = csrfField.value; + } + + return headers; +} + +export function removeCsrfToken (formElement) { + const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]'); + + if (!csrfField) { + return; + } + + const csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value'); + + if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) { + const cookie = csrfCookie + '_' + csrfField.value + '=0; path=/; samesite=strict; max-age=0'; + + document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie; + } +} + +/* stimulusFetch: 'lazy' */ +export default 'csrf-protection-controller'; diff --git a/assets/controllers/elements/attachment_autocomplete_controller.js b/assets/controllers/elements/attachment_autocomplete_controller.js index fe44baee9..94b011366 100644 --- a/assets/controllers/elements/attachment_autocomplete_controller.js +++ b/assets/controllers/elements/attachment_autocomplete_controller.js @@ -23,11 +23,22 @@ import "tom-select/dist/css/tom-select.bootstrap5.css"; import '../../css/components/tom-select_extensions.css'; import TomSelect from "tom-select"; +import TomSelect_click_to_edit from '../../tomselect/click_to_edit/click_to_edit' +import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed' + +TomSelect.define('click_to_edit', TomSelect_click_to_edit) +TomSelect.define('autoselect_typed', TomSelect_autoselect_typed) + export default class extends Controller { _tomSelect; connect() { + let dropdownParent = "body"; + if (this.element.closest('.modal')) { + dropdownParent = null + } + let settings = { persistent: false, create: true, @@ -36,6 +47,7 @@ export default class extends Controller { selectOnTab: true, //This a an ugly solution to disable the delimiter parsing of the TomSelect plugin delimiter: 'VERY_L0NG_Dโ‚ฌLIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING', + dropdownParent: dropdownParent, render: { item: (data, escape) => { return '' + escape(data.label) + ''; @@ -46,6 +58,12 @@ export default class extends Controller { } return '
' + escape(data.label) + '
'; } + }, + plugins: { + 'autoselect_typed': {}, + 'click_to_edit': {}, + 'clear_button': {}, + "restore_on_backspace": {} } }; diff --git a/assets/controllers/elements/ckeditor_controller.js b/assets/controllers/elements/ckeditor_controller.js index 079ee2ad6..46e78fd5e 100644 --- a/assets/controllers/elements/ckeditor_controller.js +++ b/assets/controllers/elements/ckeditor_controller.js @@ -23,10 +23,32 @@ import { default as FullEditor } from "../../ckeditor/markdown_full"; import { default as SingleLineEditor} from "../../ckeditor/markdown_single_line"; import { default as HTMLLabelEditor } from "../../ckeditor/html_label"; -import EditorWatchdog from '@ckeditor/ckeditor5-watchdog/src/editorwatchdog'; +import {EditorWatchdog} from 'ckeditor5'; +import "ckeditor5/ckeditor5.css";; import "../../css/components/ckeditor.css"; +const translationContext = require.context( + 'ckeditor5/translations', + false, + //Only load the translation files we will really need + /(de|it|fr|ru|ja|cs|da|zh|pl|hu)\.js$/ +); + +function loadTranslation(language) { + if (!language || language === 'en') { + return null; + } + const lang = language.slice(0, 2); + const path = `./${lang}.js`; + if (translationContext.keys().includes(path)) { + const module = translationContext(path); + return module.default; + } else { + return null; + } +} + /* stimulusFetch: 'lazy' */ export default class extends Controller { connect() { @@ -51,9 +73,22 @@ export default class extends Controller { const language = document.body.dataset.locale ?? "en"; + const emojiURL = new URL('../../ckeditor/emojis.json', import.meta.url).href; + const config = { language: language, licenseKey: "GPL", + + emoji: { + definitionsUrl: emojiURL + } + } + + //Load translations if not english + let translations = loadTranslation(language); + if (translations) { + //Keep existing translations (e.g. from other plugins), if any + config.translations = [window.CKEDITOR_TRANSLATIONS, translations]; } const watchdog = new EditorWatchdog(); @@ -84,4 +119,4 @@ export default class extends Controller { console.error(error); }); } -} \ No newline at end of file +} diff --git a/assets/controllers/elements/datatables/parts_controller.js b/assets/controllers/elements/datatables/parts_controller.js index 1fe11a200..c43fa2765 100644 --- a/assets/controllers/elements/datatables/parts_controller.js +++ b/assets/controllers/elements/datatables/parts_controller.js @@ -45,8 +45,10 @@ export default class extends DatatablesController { //Hide/Unhide panel with the selection tools if (count > 0) { selectPanel.classList.remove('d-none'); + selectPanel.classList.add('sticky-select-bar'); } else { selectPanel.classList.add('d-none'); + selectPanel.classList.remove('sticky-select-bar'); } //Update selection count text diff --git a/assets/controllers/elements/part_select_controller.js b/assets/controllers/elements/part_select_controller.js index 5abd5ba3c..8a4e19b83 100644 --- a/assets/controllers/elements/part_select_controller.js +++ b/assets/controllers/elements/part_select_controller.js @@ -10,12 +10,19 @@ export default class extends Controller { connect() { + //Check if tomselect is inside an modal and do not attach the dropdown to body in that case (as it breaks the modal) + let dropdownParent = "body"; + if (this.element.closest('.modal')) { + dropdownParent = null + } + let settings = { allowEmptyOption: true, plugins: ['dropdown_input'], searchField: ["name", "description", "category", "footprint"], valueField: "id", labelField: "name", + dropdownParent: dropdownParent, preload: "focus", render: { item: (data, escape) => { @@ -71,4 +78,4 @@ export default class extends Controller { //Destroy the TomSelect instance this._tomSelect.destroy(); } -} \ No newline at end of file +} diff --git a/assets/controllers/elements/select_controller.js b/assets/controllers/elements/select_controller.js index a96bca108..d70e588cf 100644 --- a/assets/controllers/elements/select_controller.js +++ b/assets/controllers/elements/select_controller.js @@ -38,11 +38,17 @@ export default class extends Controller { this._emptyMessage = this.element.getAttribute('title'); } + let dropdownParent = "body"; + if (this.element.closest('.modal')) { + dropdownParent = null + } let settings = { + plugins: ["clear_button"], allowEmptyOption: true, selectOnTab: true, maxOptions: null, + dropdownParent: dropdownParent, render: { item: this.renderItem.bind(this), @@ -50,7 +56,24 @@ export default class extends Controller { } }; + //Load the drag_drop plugin if the select is ordered + if (this.element.dataset.orderedValue) { + settings.plugins.push('drag_drop'); + settings.plugins.push("caret_position"); + } + + //If multiple items can be selected, enable the remove_button plugin + if (this.element.multiple) { + settings.plugins.push('remove_button'); + } + this._tomSelect = new TomSelect(this.element, settings); + + //If the select is ordered, we need to update the value field (with the decoded value from the orderedValue field) + if (this.element.dataset.orderedValue) { + const data = JSON.parse(this.element.dataset.orderedValue); + this._tomSelect.setValue(data); + } } getTomSelect() { @@ -90,4 +113,4 @@ export default class extends Controller { //Destroy the TomSelect instance this._tomSelect.destroy(); } -} \ No newline at end of file +} diff --git a/assets/controllers/elements/select_multiple_controller.js b/assets/controllers/elements/select_multiple_controller.js index 85680af0e..17e85fae3 100644 --- a/assets/controllers/elements/select_multiple_controller.js +++ b/assets/controllers/elements/select_multiple_controller.js @@ -20,13 +20,21 @@ import {Controller} from "@hotwired/stimulus"; import TomSelect from "tom-select"; +// TODO: Merge with select_controller.js + export default class extends Controller { _tomSelect; connect() { + let dropdownParent = "body"; + if (this.element.closest('.modal')) { + dropdownParent = null + } + this._tomSelect = new TomSelect(this.element, { maxItems: 1000, allowEmptyOption: true, + dropdownParent: dropdownParent, plugins: ['remove_button'], }); } @@ -37,4 +45,4 @@ export default class extends Controller { this._tomSelect.destroy(); } -} \ No newline at end of file +} diff --git a/assets/controllers/elements/static_file_autocomplete_controller.js b/assets/controllers/elements/static_file_autocomplete_controller.js index 57e488240..9703c6189 100644 --- a/assets/controllers/elements/static_file_autocomplete_controller.js +++ b/assets/controllers/elements/static_file_autocomplete_controller.js @@ -23,6 +23,12 @@ import "tom-select/dist/css/tom-select.bootstrap5.css"; import '../../css/components/tom-select_extensions.css'; import TomSelect from "tom-select"; +import TomSelect_click_to_edit from '../../tomselect/click_to_edit/click_to_edit' +import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed' + +TomSelect.define('click_to_edit', TomSelect_click_to_edit) +TomSelect.define('autoselect_typed', TomSelect_autoselect_typed) + /** * This is the frontend controller for StaticFileAutocompleteType form element. * Basically it loads a text file from the given url (via data-url) and uses it as a source for the autocomplete. @@ -34,6 +40,11 @@ export default class extends Controller { connect() { + let dropdownParent = "body"; + if (this.element.closest('.modal')) { + dropdownParent = null + } + let settings = { persistent: false, create: true, @@ -44,9 +55,16 @@ export default class extends Controller { valueField: 'text', searchField: 'text', orderField: 'text', + dropdownParent: dropdownParent, //This a an ugly solution to disable the delimiter parsing of the TomSelect plugin - delimiter: 'VERY_L0NG_Dโ‚ฌLIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING' + delimiter: 'VERY_L0NG_Dโ‚ฌLIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING', + plugins: { + 'autoselect_typed': {}, + 'click_to_edit': {}, + 'clear_button': {}, + 'restore_on_backspace': {} + } }; if (this.element.dataset.url) { diff --git a/assets/controllers/elements/structural_entity_select_controller.js b/assets/controllers/elements/structural_entity_select_controller.js index 4735dd2a7..4b220d5b5 100644 --- a/assets/controllers/elements/structural_entity_select_controller.js +++ b/assets/controllers/elements/structural_entity_select_controller.js @@ -24,6 +24,9 @@ import {Controller} from "@hotwired/stimulus"; import {trans, ENTITY_SELECT_GROUP_NEW_NOT_ADDED_TO_DB} from '../../translator.js' +import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed' +TomSelect.define('autoselect_typed', TomSelect_autoselect_typed) + export default class extends Controller { _tomSelect; @@ -37,16 +40,24 @@ export default class extends Controller { const allowAdd = this.element.getAttribute("data-allow-add") === "true"; const addHint = this.element.getAttribute("data-add-hint") ?? ""; + let dropdownParent = "body"; + if (this.element.closest('.modal')) { + dropdownParent = null + } + + let settings = { allowEmptyOption: true, selectOnTab: true, maxOptions: null, create: allowAdd ? this.createItem.bind(this) : false, + createFilter: this.createFilter.bind(this), // This three options allow us to paste element names with commas: (see issue #538) maxItems: 1, delimiter: "$$VERY_LONG_DELIMITER_THAT_SHOULD_NEVER_APPEAR$$", splitOn: null, + dropdownParent: dropdownParent, searchField: [ {field: "text", weight : 2}, @@ -81,8 +92,17 @@ export default class extends Controller { //Add callbacks to update validity onInitialize: this.updateValidity.bind(this), onChange: this.updateValidity.bind(this), + + plugins: { + "autoselect_typed": {}, + } }; + //Add clear button plugin, if an empty option is present + if (this.element.querySelector("option[value='']") !== null) { + settings.plugins["clear_button"] = {}; + } + this._tomSelect = new TomSelect(this.element, settings); //Do not do a sync here as this breaks the initial rendering of the empty option //this._tomSelect.sync(); @@ -113,6 +133,31 @@ export default class extends Controller { }); } + createFilter(input) { + + //Normalize the input (replace spacing around arrows) + if (input.includes("->")) { + const inputs = input.split("->"); + inputs.forEach((value, index) => { + inputs[index] = value.trim(); + }); + input = inputs.join("->"); + } else { + input = input.trim(); + } + + const options = this._tomSelect.options; + //Iterate over all options and check if the input is already present + for (let index in options) { + const option = options[index]; + if (option.path === input) { + return false; + } + } + + return true; + } + updateValidity() { //Mark this input as invalid, if the selected option is disabled diff --git a/assets/controllers/elements/tagsinput_controller.js b/assets/controllers/elements/tagsinput_controller.js index acfcd0fad..14725227a 100644 --- a/assets/controllers/elements/tagsinput_controller.js +++ b/assets/controllers/elements/tagsinput_controller.js @@ -23,19 +23,32 @@ import "tom-select/dist/css/tom-select.bootstrap5.css"; import '../../css/components/tom-select_extensions.css'; import TomSelect from "tom-select"; +import TomSelect_click_to_edit from '../../tomselect/click_to_edit/click_to_edit' +import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed' + +TomSelect.define('click_to_edit', TomSelect_click_to_edit) +TomSelect.define('autoselect_typed', TomSelect_autoselect_typed) + export default class extends Controller { _tomSelect; connect() { + let dropdownParent = "body"; + if (this.element.closest('.modal')) { + dropdownParent = null + } + let settings = { plugins: { - remove_button:{ - } + remove_button:{}, + 'autoselect_typed': {}, + 'click_to_edit': {}, }, persistent: false, selectOnTab: true, createOnBlur: true, create: true, + dropdownParent: dropdownParent, }; if(this.element.dataset.autocomplete) { @@ -66,4 +79,4 @@ export default class extends Controller { //Destroy the TomSelect instance this._tomSelect.destroy(); } -} \ No newline at end of file +} diff --git a/assets/controllers/field_mapping_controller.js b/assets/controllers/field_mapping_controller.js new file mode 100644 index 000000000..9c9c8ac65 --- /dev/null +++ b/assets/controllers/field_mapping_controller.js @@ -0,0 +1,136 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["tbody", "addButton", "submitButton"] + static values = { + mappingIndex: Number, + maxMappings: Number, + prototype: String, + maxMappingsReachedMessage: String + } + + connect() { + this.updateAddButtonState() + this.updateFieldOptions() + this.attachEventListeners() + } + + attachEventListeners() { + // Add event listeners to existing field selects + const fieldSelects = this.tbodyTarget.querySelectorAll('select[name*="[field]"]') + fieldSelects.forEach(select => { + select.addEventListener('change', this.updateFieldOptions.bind(this)) + }) + + // Note: Add button click is handled by Stimulus action in template (data-action="http://github.com/click-%3Efield-mapping#addMapping") + // No manual event listener needed + + // Form submit handler + const form = this.element.querySelector('form') + if (form && this.hasSubmitButtonTarget) { + form.addEventListener('submit', this.handleFormSubmit.bind(this)) + } + } + + addMapping() { + const currentMappings = this.tbodyTarget.querySelectorAll('.mapping-row').length + + if (currentMappings >= this.maxMappingsValue) { + alert(this.maxMappingsReachedMessageValue) + return + } + + const newRowHtml = this.prototypeValue.replace(/__name__/g, this.mappingIndexValue) + const tempDiv = document.createElement('div') + tempDiv.innerHTML = newRowHtml + + const fieldWidget = tempDiv.querySelector('select[name*="[field]"]') || tempDiv.children[0] + const providerWidget = tempDiv.querySelector('select[name*="[providers]"]') || tempDiv.children[1] + const priorityWidget = tempDiv.querySelector('input[name*="[priority]"]') || tempDiv.children[2] + + const newRow = document.createElement('tr') + newRow.className = 'mapping-row' + newRow.innerHTML = ` + ${fieldWidget ? fieldWidget.outerHTML : ''} + ${providerWidget ? providerWidget.outerHTML : ''} + ${priorityWidget ? priorityWidget.outerHTML : ''} + + + + ` + + this.tbodyTarget.appendChild(newRow) + this.mappingIndexValue++ + + const newFieldSelect = newRow.querySelector('select[name*="[field]"]') + if (newFieldSelect) { + newFieldSelect.value = '' + newFieldSelect.addEventListener('change', this.updateFieldOptions.bind(this)) + } + + this.updateFieldOptions() + this.updateAddButtonState() + } + + removeMapping(event) { + const row = event.target.closest('tr') + row.remove() + this.updateFieldOptions() + this.updateAddButtonState() + } + + updateFieldOptions() { + const fieldSelects = this.tbodyTarget.querySelectorAll('select[name*="[field]"]') + + const selectedFields = Array.from(fieldSelects) + .map(select => select.value) + .filter(value => value && value !== '') + + fieldSelects.forEach(select => { + Array.from(select.options).forEach(option => { + const isCurrentValue = option.value === select.value + const isEmptyOption = !option.value || option.value === '' + const isAlreadySelected = selectedFields.includes(option.value) + + if (!isEmptyOption && isAlreadySelected && !isCurrentValue) { + option.disabled = true + option.style.display = 'none' + } else { + option.disabled = false + option.style.display = '' + } + }) + }) + } + + updateAddButtonState() { + const currentMappings = this.tbodyTarget.querySelectorAll('.mapping-row').length + + if (this.hasAddButtonTarget) { + if (currentMappings >= this.maxMappingsValue) { + this.addButtonTarget.disabled = true + this.addButtonTarget.title = this.maxMappingsReachedMessageValue + } else { + this.addButtonTarget.disabled = false + this.addButtonTarget.title = '' + } + } + } + + handleFormSubmit(event) { + if (this.hasSubmitButtonTarget) { + this.submitButtonTarget.disabled = true + + // Disable the entire form to prevent changes during processing + const form = event.target + const formElements = form.querySelectorAll('input, select, textarea, button') + formElements.forEach(element => { + if (element !== this.submitButtonTarget) { + element.disabled = true + } + }) + } + } +} \ No newline at end of file diff --git a/assets/controllers/pages/latex_preview_controller.js b/assets/controllers/pages/latex_preview_controller.js index c836faa66..7f1e611c0 100644 --- a/assets/controllers/pages/latex_preview_controller.js +++ b/assets/controllers/pages/latex_preview_controller.js @@ -25,9 +25,23 @@ import "katex/dist/katex.css"; export default class extends Controller { static targets = ["input", "preview"]; + static values = { + unit: {type: Boolean, default: false} //Render as upstanding (non-italic) text, useful for units + } + updatePreview() { - katex.render(this.inputTarget.value, this.previewTarget, { + let value = ""; + if (this.unitValue) { + //Escape percentage signs + value = this.inputTarget.value.replace(/%/g, '\\%'); + + value = "\\mathrm{" + value + "}"; + } else { + value = this.inputTarget.value; + } + + katex.render(value, this.previewTarget, { throwOnError: false, }); } diff --git a/assets/controllers/pages/parameters_autocomplete_controller.js b/assets/controllers/pages/parameters_autocomplete_controller.js index f65049904..e187aa42e 100644 --- a/assets/controllers/pages/parameters_autocomplete_controller.js +++ b/assets/controllers/pages/parameters_autocomplete_controller.js @@ -22,6 +22,13 @@ import TomSelect from "tom-select"; import katex from "katex"; import "katex/dist/katex.css"; + +import TomSelect_click_to_edit from '../../tomselect/click_to_edit/click_to_edit' +import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed' + +TomSelect.define('click_to_edit', TomSelect_click_to_edit) +TomSelect.define('autoselect_typed', TomSelect_autoselect_typed) + /* stimulusFetch: 'lazy' */ export default class extends Controller { @@ -53,7 +60,10 @@ export default class extends Controller connect() { const settings = { plugins: { - clear_button:{} + 'autoselect_typed': {}, + 'click_to_edit': {}, + 'clear_button': {}, + 'restore_on_backspace': {} }, persistent: false, maxItems: 1, @@ -75,7 +85,9 @@ export default class extends Controller tmp += '' + katex.renderToString(data.symbol) + '' } if (data.unit) { - tmp += '' + katex.renderToString('[' + data.unit + ']') + '' + let unit = data.unit.replace(/%/g, '\\%'); + unit = "\\mathrm{" + unit + "}"; + tmp += '' + katex.renderToString('[' + unit + ']') + '' } diff --git a/assets/controllers/toggle_password_controller.js b/assets/controllers/toggle_password_controller.js new file mode 100644 index 000000000..bef87e110 --- /dev/null +++ b/assets/controllers/toggle_password_controller.js @@ -0,0 +1,86 @@ +/* + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2025 Jan Bรถhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { Controller } from '@hotwired/stimulus'; +import '../css/components/toggle_password.css'; + +export default class extends Controller { + static values = { + visibleLabel: { type: String, default: 'Show' }, + visibleIcon: { type: String, default: 'Default' }, + hiddenLabel: { type: String, default: 'Hide' }, + hiddenIcon: { type: String, default: 'Default' }, + buttonClasses: Array, + }; + + isDisplayed = false; + visibleIcon = ` + + +`; + hiddenIcon = ` + + +`; + + connect() { + if (this.visibleIconValue !== 'Default') { + this.visibleIcon = this.visibleIconValue; + } + + if (this.hiddenIconValue !== 'Default') { + this.hiddenIcon = this.hiddenIconValue; + } + + const button = this.createButton(); + + this.element.insertAdjacentElement('afterend', button); + this.dispatchEvent('connect', { element: this.element, button }); + } + + /** + * @returns {HTMLButtonElement} + */ + createButton() { + const button = document.createElement('button'); + button.type = 'button'; + button.classList.add(...this.buttonClassesValue); + button.setAttribute('tabindex', '-1'); + button.addEventListener('click', this.toggle.bind(this)); + button.innerHTML = `${this.visibleIcon} ${this.visibleLabelValue}`; + return button; + } + + /** + * Toggle input type between "text" or "password" and update label accordingly + */ + toggle(event) { + this.isDisplayed = !this.isDisplayed; + const toggleButtonElement = event.currentTarget; + toggleButtonElement.innerHTML = this.isDisplayed + ? `${this.hiddenIcon} ${this.hiddenLabelValue}` + : `${this.visibleIcon} ${this.visibleLabelValue}`; + this.element.setAttribute('type', this.isDisplayed ? 'text' : 'password'); + this.dispatchEvent(this.isDisplayed ? 'show' : 'hide', { element: this.element, button: toggleButtonElement }); + } + + dispatchEvent(name, payload) { + this.dispatch(name, { detail: payload, prefix: 'toggle-password' }); + } +} diff --git a/assets/css/app/bs-overrides.css b/assets/css/app/bs-overrides.css index 070f353d3..ec5a8f7c7 100644 --- a/assets/css/app/bs-overrides.css +++ b/assets/css/app/bs-overrides.css @@ -120,4 +120,11 @@ ins { del { background-color: #f09595; font-weight: bold; -} \ No newline at end of file +} + +/**************************************** + * Password toggle + ****************************************/ +.toggle-password-button { + top: 0.7rem !important; +} diff --git a/assets/css/app/helpers.css b/assets/css/app/helpers.css index 2741d6672..8e7b6fa30 100644 --- a/assets/css/app/helpers.css +++ b/assets/css/app/helpers.css @@ -111,4 +111,11 @@ ul.structural_link li a:hover { .permission-checkbox:checked { background-color: var(--bs-success); border-color: var(--bs-success); +} + +/*********************************************** + * Katex rendering with same height as text + ***********************************************/ +.katex-same-height-as-text .katex { + font-size: 1.0em; } \ No newline at end of file diff --git a/assets/css/app/images.css b/assets/css/app/images.css index 214776e78..0212a85b7 100644 --- a/assets/css/app/images.css +++ b/assets/css/app/images.css @@ -18,8 +18,8 @@ */ .hoverpic { - min-width: 10px; - max-width: 30px; + min-width: var(--table-image-preview-min-size, 20px); + max-width: var(--table-image-preview-max-size, 35px); display: block; margin-left: auto; margin-right: auto; @@ -49,7 +49,7 @@ } .part-table-image { - max-height: 40px; + max-height: calc(1.2*var(--table-image-preview-max-size, 35px)); /** Aspect ratio of maximum 1.2 */ object-fit: contain; } diff --git a/assets/css/app/tables.css b/assets/css/app/tables.css index ae892f508..b2d8882c2 100644 --- a/assets/css/app/tables.css +++ b/assets/css/app/tables.css @@ -17,6 +17,16 @@ * along with this program. If not, see . */ +/**************************************** + * Action bar + ****************************************/ + +.sticky-select-bar { + position: sticky; + top: 120px; + z-index: 1000; /* Ensure the bar is above other content */ +} + /**************************************** * Tables ****************************************/ @@ -84,6 +94,11 @@ th.select-checkbox { display: inline-flex; } +/** Add spacing between column visibility button and length menu */ +.buttons-colvis { + margin-right: 0.2em !important; +} + /** Fix datatables select-checkbox position */ table.dataTable tr.selected td.select-checkbox:after { @@ -109,4 +124,4 @@ Classes for Datatables export #export-messageTop, .export-helper{ display: none; -} \ No newline at end of file +} diff --git a/assets/css/components/ckeditor.css b/assets/css/components/ckeditor.css index d6b3def48..5f093bf24 100644 --- a/assets/css/components/ckeditor.css +++ b/assets/css/components/ckeditor.css @@ -71,6 +71,8 @@ --ck-color-button-on-hover-background: var(--bs-secondary-bg); --ck-color-button-on-active-background: var(--bs-secondary-bg); --ck-color-button-on-disabled-background: var(--bs-secondary-bg); - --ck-color-button-on-color: var(--bs-primary) + --ck-color-button-on-color: var(--bs-primary); -} \ No newline at end of file + --ck-content-font-color: var(--ck-color-base-text); + +} diff --git a/assets/css/components/toggle_password.css b/assets/css/components/toggle_password.css new file mode 100644 index 000000000..f1f4a889e --- /dev/null +++ b/assets/css/components/toggle_password.css @@ -0,0 +1,41 @@ +/* + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2025 Jan Bรถhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +.toggle-password-container { + position: relative; +} +.toggle-password-icon { + height: 1rem; + width: 1rem; +} +.toggle-password-button { + align-items: center; + background-color: transparent; + border: none; + column-gap: 0.25rem; + display: flex; + flex-direction: row; + font-size: 0.875rem; + justify-items: center; + height: 1rem; + line-height: 1.25rem; + position: absolute; + right: 0.5rem; + top: -1.25rem; +} diff --git a/assets/js/app.js b/assets/js/app.js index 43acec5db..54b736769 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -49,7 +49,7 @@ window.$ = window.jQuery = require("jquery"); //Use the local WASM file for the ZXing library import { setZXingModuleOverrides, -} from "barcode-detector/pure"; +} from "barcode-detector/ponyfill"; import wasmFile from "../../node_modules/zxing-wasm/dist/reader/zxing_reader.wasm"; setZXingModuleOverrides({ locateFile: (path, prefix) => { @@ -58,4 +58,4 @@ setZXingModuleOverrides({ } return prefix + path; }, -}); \ No newline at end of file +}); diff --git a/assets/js/lib/datatables.js b/assets/js/lib/datatables.js index 8e39548bc..67bab02db 100644 --- a/assets/js/lib/datatables.js +++ b/assets/js/lib/datatables.js @@ -75,11 +75,10 @@ request._dt = config.name; //Try to resolve the original column index when the column was reordered (using the ColReorder plugin) - //Only do this when _ColReorder_iOrigCol is available - if (settings.aoColumns && settings.aoColumns.length && settings.aoColumns[0]._ColReorder_iOrigCol !== undefined) { + if (dt.colReorder && dt.colReorder.transpose) { if (request.order && request.order.length) { request.order.forEach(function (order) { - order.column = settings.aoColumns[order.column]._ColReorder_iOrigCol; + order.column = dt.colReorder.transpose(order.column, "toOriginal"); }); } } diff --git a/assets/tomselect/autoselect_typed/autoselect_typed.js b/assets/tomselect/autoselect_typed/autoselect_typed.js new file mode 100644 index 000000000..8a426be7d --- /dev/null +++ b/assets/tomselect/autoselect_typed/autoselect_typed.js @@ -0,0 +1,63 @@ +/** + * Autoselect Typed plugin for Tomselect + * + * This plugin allows automatically selecting an option matching the typed text when the Tomselect element goes out of + * focus (is blurred) and/or when the delimiter is typed. + * + * #select_on_blur option + * Tomselect natively supports the "createOnBlur" option. This option picks up any remaining text in the input field + * and uses it to create a new option and selects that option. It does behave a bit strangely though, in that it will + * not select an already existing option when the input is blurred, so if you typed something that matches an option in + * the list and then click outside the box (without pressing enter) the entered text is just removed (unless you have + * allow duplicates on in which case it will create a new option). + * This plugin fixes that, such that Tomselect will first try to select an option matching the remaining uncommitted + * text and only when no matching option is found tries to create a new one (if createOnBlur and create is on) + * + * #select_on_delimiter option + * Normally when typing the delimiter (space by default) Tomselect will try to create a new option (and select it) (if + * create is on), but if the typed text matches an option (and allow duplicates is off) it refuses to react at all until + * you press enter. With this option, the delimiter will also allow selecting an option, not just creating it. + */ +function select_current_input(self){ + if(self.isLocked){ + return + } + + const val = self.inputValue() + //Do nothing if the input is empty + if (!val) { + return + } + + if (self.options[val]) { + self.addItem(val) + self.setTextboxValue() + } +} + +export default function(plugin_options_) { + const plugin_options = Object.assign({ + //Autoselect the typed text when the input element goes out of focus + select_on_blur: true, + //Autoselect the typed text when the delimiter is typed + select_on_delimiter: true, + }, plugin_options_); + + const self = this + + if(plugin_options.select_on_blur) { + this.hook("before", "onBlur", function () { + select_current_input(self) + }) + } + + if(plugin_options.select_on_delimiter) { + this.hook("before", "onKeyPress", function (e) { + const character = String.fromCharCode(e.keyCode || e.which); + if (self.settings.mode === 'multi' && character === self.settings.delimiter) { + select_current_input(self) + } + }) + } + +} \ No newline at end of file diff --git a/assets/tomselect/click_to_edit/click_to_edit.js b/assets/tomselect/click_to_edit/click_to_edit.js new file mode 100644 index 000000000..b7dcab030 --- /dev/null +++ b/assets/tomselect/click_to_edit/click_to_edit.js @@ -0,0 +1,93 @@ +/** + * click_to_edit plugin for Tomselect + * + * This plugin allows editing (and selecting text in) any selected item by clicking it. + * + * Usually, when the user typed some text and created an item in Tomselect that item cannot be edited anymore. To make + * a change, the item has to be deleted and retyped completely. There is also generally no way to copy text out of a + * tomselect item. The "restore_on_backspace" plugin improves that somewhat, by allowing the user to edit an item after + * pressing backspace. However, it is somewhat confusing to first have to focus the field an then hit backspace in order + * to copy a piece of text. It may also not be immediately obvious for editing. + * This plugin transforms an item into editable text when it is clicked, e.g. when the user tries to place the caret + * within an item or when they try to drag across the text to highlight it. + * It also plays nice with the remove_button plugin which still removes (deselects) an option entirely. + * + * It is recommended to also enable the autoselect_typed plugin when using this plugin. Without it, the text in the + * input field (i.e. the item that was just clicked) is lost when the user clicks outside the field. Also, when the user + * clicks an option (making it text) and then tries to enter another one by entering the delimiter (e.g. space) nothing + * happens until enter is pressed or the text is changed from what it was. + */ + +/** + * Return a dom element from either a dom query string, jQuery object, a dom element or html string + * https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro/35385518#35385518 + * + * param query should be {} + */ +const getDom = query => { + if (query.jquery) { + return query[0]; + } + if (query instanceof HTMLElement) { + return query; + } + if (isHtmlString(query)) { + var tpl = document.createElement('template'); + tpl.innerHTML = query.trim(); // Never return a text node of whitespace as the result + return tpl.content.firstChild; + } + return document.querySelector(query); +}; +const isHtmlString = arg => { + if (typeof arg === 'string' && arg.indexOf('<') > -1) { + return true; + } + return false; +}; + +function plugin(plugin_options_) { + const self = this + + const plugin_options = Object.assign({ + //If there is unsubmitted text in the input field, should that text be automatically used to select a matching + //element? If this is off, clicking on item1 and then clicking on item2 will result in item1 being deselected + auto_select_before_edit: true, + //If there is unsubmitted text in the input field, should that text be automatically used to create a matching + //element if no matching element was found or auto_select_before_edit is off? + auto_create_before_edit: true, + //customize this function to change which text the item is replaced with when clicking on it + text: option => { + return option[self.settings.labelField]; + } + }, plugin_options_); + + + self.hook('after', 'setupTemplates', () => { + const orig_render_item = self.settings.render.item; + self.settings.render.item = (data, escape) => { + const item = getDom(orig_render_item.call(self, data, escape)); + + item.addEventListener('click', evt => { + if (self.isLocked) { + return; + } + const val = self.inputValue(); + + if (self.options[val]) { + self.addItem(val) + } else if (self.settings.create) { + self.createItem(); + } + const option = self.options[item.dataset.value] + self.setTextboxValue(plugin_options.text.call(self, option)); + self.focus(); + self.removeItem(item); + } + ); + + return item; + } + }); + +} +export { plugin as default }; \ No newline at end of file diff --git a/assets/tomselect/extend_existing_selection/extend_existing_selection.js b/assets/tomselect/extend_existing_selection/extend_existing_selection.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/composer.json b/composer.json index 11026fa29..f53130d4f 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "type": "project", "license": "AGPL-3.0-or-later", "require": { - "php": "^8.1", + "php": "^8.2", "ext-ctype": "*", "ext-dom": "*", "ext-gd": "*", @@ -12,74 +12,82 @@ "ext-json": "*", "ext-mbstring": "*", "amphp/http-client": "^5.1", - "api-platform/core": "^3.1", + "api-platform/doctrine-orm": "^4.1", + "api-platform/json-api": "^4.0.0", + "api-platform/symfony": "^4.0.0", "beberlei/doctrineextensions": "^1.2", - "brick/math": "0.12.1 as 0.11.0", - "composer/ca-bundle": "^1.3", + "brick/math": "^0.13.1", + "composer/ca-bundle": "^1.5", "composer/package-versions-deprecated": "^1.11.99.5", "doctrine/data-fixtures": "^2.0.0", "doctrine/dbal": "^4.0.0", "doctrine/doctrine-bundle": "^2.0", "doctrine/doctrine-migrations-bundle": "^3.0", "doctrine/orm": "^3.2.0", - "dompdf/dompdf": "^v3.0.0", - "erusev/parsedown": "^1.7", - "florianv/swap": "^4.0", - "florianv/swap-bundle": "dev-master", + "dompdf/dompdf": "^3.1.2", "gregwar/captcha-bundle": "^2.1.0", "hshn/base64-encoded-file": "^5.0", - "jbtronics/2fa-webauthn": "^v2.2.0", + "jbtronics/2fa-webauthn": "^3.0.0", "jbtronics/dompdf-font-loader-bundle": "^1.0.0", + "jbtronics/settings-bundle": "^3.0.0", "jfcherng/php-diff": "^6.14", "knpuniversity/oauth2-client-bundle": "^2.15", + "league/commonmark": "^2.7", "league/csv": "^9.8.0", "league/html-to-markdown": "^5.0.1", "liip/imagine-bundle": "^2.2", - "nbgrp/onelogin-saml-bundle": "^1.3", + "maennchen/zipstream-php": "2.1", + "nbgrp/onelogin-saml-bundle": "^v2.0.2", "nelexa/zip": "^4.0", "nelmio/cors-bundle": "^2.3", "nelmio/security-bundle": "^3.0", "nyholm/psr7": "^1.1", - "omines/datatables-bundle": "^0.9.1", + "omines/datatables-bundle": "^0.10.0", "paragonie/sodium_compat": "^1.21", "part-db/label-fonts": "^1.0", + "part-db/swap-bundle": "^6.0.0", + "phpoffice/phpspreadsheet": "^5.0.0", + "rhukster/dom-sanitizer": "^1.0", "runtime/frankenphp-symfony": "^0.2.0", "s9e/text-formatter": "^2.1", - "scheb/2fa-backup-code": "^6.8.0", - "scheb/2fa-bundle": "^6.8.0", - "scheb/2fa-google-authenticator": "^6.8.0", - "scheb/2fa-trusted-device": "^6.8.0", + "scheb/2fa-backup-code": "^v7.11.0", + "scheb/2fa-bundle": "^v7.11.0", + "scheb/2fa-google-authenticator": "^v7.11.0", + "scheb/2fa-trusted-device": "^v7.11.0", "shivas/versioning-bundle": "^4.0", "spatie/db-dumper": "^3.3.1", "symfony/apache-pack": "^1.0", - "symfony/asset": "6.4.*", - "symfony/console": "6.4.*", - "symfony/dotenv": "6.4.*", - "symfony/expression-language": "6.4.*", + "symfony/asset": "7.3.*", + "symfony/console": "7.3.*", + "symfony/css-selector": "7.3.*", + "symfony/dom-crawler": "7.3.*", + "symfony/dotenv": "7.3.*", + "symfony/expression-language": "7.3.*", "symfony/flex": "^v2.3.1", - "symfony/form": "6.4.*", - "symfony/framework-bundle": "6.4.*", - "symfony/http-client": "6.4.*", - "symfony/http-kernel": "6.4.*", - "symfony/mailer": "6.4.*", + "symfony/form": "7.3.*", + "symfony/framework-bundle": "7.3.*", + "symfony/http-client": "7.3.*", + "symfony/http-kernel": "7.3.*", + "symfony/mailer": "7.3.*", "symfony/monolog-bundle": "^3.1", "symfony/polyfill-php82": "^1.28", - "symfony/process": "6.4.*", - "symfony/property-access": "6.4.*", - "symfony/property-info": "6.4.*", - "symfony/rate-limiter": "6.4.*", - "symfony/runtime": "6.4.*", - "symfony/security-bundle": "6.4.*", - "symfony/serializer": "6.4.*", - "symfony/string": "6.4.*", - "symfony/translation": "6.4.*", - "symfony/twig-bundle": "6.4.*", + "symfony/process": "7.3.*", + "symfony/property-access": "7.3.*", + "symfony/property-info": "7.3.*", + "symfony/rate-limiter": "7.3.*", + "symfony/runtime": "7.3.*", + "symfony/security-bundle": "7.3.*", + "symfony/serializer": "7.3.*", + "symfony/string": "7.3.*", + "symfony/translation": "7.3.*", + "symfony/twig-bundle": "7.3.*", "symfony/ux-translator": "^2.10", "symfony/ux-turbo": "^2.0", - "symfony/validator": "6.4.*", - "symfony/web-link": "6.4.*", + "symfony/validator": "7.3.*", + "symfony/web-link": "7.3.*", "symfony/webpack-encore-bundle": "^v2.0.1", - "symfony/yaml": "6.4.*", + "symfony/yaml": "7.3.*", + "symplify/easy-coding-standard": "^12.5.20", "tecnickcom/tc-lib-barcode": "^2.1.4", "twig/cssinliner-extra": "^3.0", "twig/extra-bundle": "^3.8", @@ -88,7 +96,7 @@ "twig/intl-extra": "^3.8", "twig/markdown-extra": "^3.8", "twig/string-extra": "^3.8", - "web-auth/webauthn-symfony-bundle": "^4.0.0" + "web-auth/webauthn-symfony-bundle": "^5.0.0" }, "require-dev": { "dama/doctrine-test-bundle": "^v8.0.0", @@ -100,17 +108,15 @@ "phpstan/phpstan-doctrine": "^2.0.1", "phpstan/phpstan-strict-rules": "^2.0.1", "phpstan/phpstan-symfony": "^2.0.0", - "phpunit/phpunit": "^9.5", + "phpunit/phpunit": "^11.5.0", "rector/rector": "^2.0.4", "roave/security-advisories": "dev-latest", - "symfony/browser-kit": "6.4.*", - "symfony/css-selector": "6.4.*", - "symfony/debug-bundle": "6.4.*", + "symfony/browser-kit": "7.3.*", + "symfony/debug-bundle": "7.3.*", "symfony/maker-bundle": "^1.13", - "symfony/phpunit-bridge": "6.4.*", - "symfony/stopwatch": "6.4.*", - "symfony/web-profiler-bundle": "6.4.*", - "symplify/easy-coding-standard": "^12.0" + "symfony/phpunit-bridge": "7.3.*", + "symfony/stopwatch": "7.3.*", + "symfony/web-profiler-bundle": "7.3.*" }, "suggest": { "ext-bcmath": "Used to improve price calculation performance", @@ -121,7 +127,7 @@ "*": "dist" }, "platform": { - "php": "8.1.0" + "php": "8.2.0" }, "sort-packages": true, "allow-plugins": { @@ -153,7 +159,7 @@ "post-update-cmd": [ "@auto-scripts" ], - "phpstan": "vendor/bin/phpstan analyse src --level 5 --memory-limit 1G" + "phpstan": "php -d memory_limit=1G vendor/bin/phpstan analyse src --level 5" }, "conflict": { "symfony/symfony": "*" @@ -161,7 +167,7 @@ "extra": { "symfony": { "allow-contrib": false, - "require": "6.4.*", + "require": "7.3.*", "docker": true } } diff --git a/composer.lock b/composer.lock index a4bae3563..72e83e0fa 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ca8701d95e24bae5d28ccdcfe242e8e4", + "content-hash": "3b5a603cc4c289262a2e58b0f37ee42e", "packages": [ { "name": "amphp/amp", - "version": "v3.0.2", + "version": "v3.1.1", "source": { "type": "git", "url": "https://github.com/amphp/amp.git", - "reference": "138801fb68cfc9c329da8a7b39d01ce7291ee4b0" + "reference": "fa0ab33a6f47a82929c38d03ca47ebb71086a93f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/138801fb68cfc9c329da8a7b39d01ce7291ee4b0", - "reference": "138801fb68cfc9c329da8a7b39d01ce7291ee4b0", + "url": "https://api.github.com/repos/amphp/amp/zipball/fa0ab33a6f47a82929c38d03ca47ebb71086a93f", + "reference": "fa0ab33a6f47a82929c38d03ca47ebb71086a93f", "shasum": "" }, "require": { @@ -77,7 +77,7 @@ ], "support": { "issues": "https://github.com/amphp/amp/issues", - "source": "https://github.com/amphp/amp/tree/v3.0.2" + "source": "https://github.com/amphp/amp/tree/v3.1.1" }, "funding": [ { @@ -85,20 +85,20 @@ "type": "github" } ], - "time": "2024-05-10T21:37:46+00:00" + "time": "2025-08-27T21:42:00+00:00" }, { "name": "amphp/byte-stream", - "version": "v2.1.1", + "version": "v2.1.2", "source": { "type": "git", "url": "https://github.com/amphp/byte-stream.git", - "reference": "daa00f2efdbd71565bf64ffefa89e37542addf93" + "reference": "55a6bd071aec26fa2a3e002618c20c35e3df1b46" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/byte-stream/zipball/daa00f2efdbd71565bf64ffefa89e37542addf93", - "reference": "daa00f2efdbd71565bf64ffefa89e37542addf93", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/55a6bd071aec26fa2a3e002618c20c35e3df1b46", + "reference": "55a6bd071aec26fa2a3e002618c20c35e3df1b46", "shasum": "" }, "require": { @@ -152,7 +152,7 @@ ], "support": { "issues": "https://github.com/amphp/byte-stream/issues", - "source": "https://github.com/amphp/byte-stream/tree/v2.1.1" + "source": "https://github.com/amphp/byte-stream/tree/v2.1.2" }, "funding": [ { @@ -160,7 +160,7 @@ "type": "github" } ], - "time": "2024-02-17T04:49:38+00:00" + "time": "2025-03-16T17:10:27+00:00" }, { "name": "amphp/cache", @@ -229,16 +229,16 @@ }, { "name": "amphp/dns", - "version": "v2.3.0", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/amphp/dns.git", - "reference": "166c43737cef1b77782c648a9d9ed11ee0c9859f" + "reference": "78eb3db5fc69bf2fc0cb503c4fcba667bc223c71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/dns/zipball/166c43737cef1b77782c648a9d9ed11ee0c9859f", - "reference": "166c43737cef1b77782c648a9d9ed11ee0c9859f", + "url": "https://api.github.com/repos/amphp/dns/zipball/78eb3db5fc69bf2fc0cb503c4fcba667bc223c71", + "reference": "78eb3db5fc69bf2fc0cb503c4fcba667bc223c71", "shasum": "" }, "require": { @@ -306,7 +306,7 @@ ], "support": { "issues": "https://github.com/amphp/dns/issues", - "source": "https://github.com/amphp/dns/tree/v2.3.0" + "source": "https://github.com/amphp/dns/tree/v2.4.0" }, "funding": [ { @@ -314,7 +314,7 @@ "type": "github" } ], - "time": "2024-12-21T01:15:34+00:00" + "time": "2025-01-19T15:43:40+00:00" }, { "name": "amphp/hpack", @@ -456,16 +456,16 @@ }, { "name": "amphp/http-client", - "version": "v5.2.1", + "version": "v5.3.4", "source": { "type": "git", "url": "https://github.com/amphp/http-client.git", - "reference": "2117f7e7cd1ecf35d4d0daea1ba5dc6fd318b114" + "reference": "75ad21574fd632594a2dd914496647816d5106bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/http-client/zipball/2117f7e7cd1ecf35d4d0daea1ba5dc6fd318b114", - "reference": "2117f7e7cd1ecf35d4d0daea1ba5dc6fd318b114", + "url": "https://api.github.com/repos/amphp/http-client/zipball/75ad21574fd632594a2dd914496647816d5106bc", + "reference": "75ad21574fd632594a2dd914496647816d5106bc", "shasum": "" }, "require": { @@ -483,8 +483,11 @@ "psr/http-message": "^1 | ^2", "revolt/event-loop": "^1" }, + "conflict": { + "amphp/file": "<3 | >=5" + }, "require-dev": { - "amphp/file": "^3", + "amphp/file": "^3 | ^4", "amphp/http-server": "^3", "amphp/php-cs-fixer-config": "^2", "amphp/phpunit-util": "^3", @@ -539,7 +542,7 @@ ], "support": { "issues": "https://github.com/amphp/http-client/issues", - "source": "https://github.com/amphp/http-client/tree/v5.2.1" + "source": "https://github.com/amphp/http-client/tree/v5.3.4" }, "funding": [ { @@ -547,7 +550,7 @@ "type": "github" } ], - "time": "2024-12-13T16:16:08+00:00" + "time": "2025-08-16T20:41:23+00:00" }, { "name": "amphp/parser", @@ -613,16 +616,16 @@ }, { "name": "amphp/pipeline", - "version": "v1.2.1", + "version": "v1.2.3", "source": { "type": "git", "url": "https://github.com/amphp/pipeline.git", - "reference": "66c095673aa5b6e689e63b52d19e577459129ab3" + "reference": "7b52598c2e9105ebcddf247fc523161581930367" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/pipeline/zipball/66c095673aa5b6e689e63b52d19e577459129ab3", - "reference": "66c095673aa5b6e689e63b52d19e577459129ab3", + "url": "https://api.github.com/repos/amphp/pipeline/zipball/7b52598c2e9105ebcddf247fc523161581930367", + "reference": "7b52598c2e9105ebcddf247fc523161581930367", "shasum": "" }, "require": { @@ -668,7 +671,7 @@ ], "support": { "issues": "https://github.com/amphp/pipeline/issues", - "source": "https://github.com/amphp/pipeline/tree/v1.2.1" + "source": "https://github.com/amphp/pipeline/tree/v1.2.3" }, "funding": [ { @@ -676,7 +679,7 @@ "type": "github" } ], - "time": "2024-07-04T00:56:47+00:00" + "time": "2025-03-16T16:33:53+00:00" }, { "name": "amphp/process", @@ -964,194 +967,64 @@ "time": "2024-08-03T19:31:26+00:00" }, { - "name": "api-platform/core", - "version": "v3.4.10", + "name": "api-platform/doctrine-common", + "version": "v4.2.2", "source": { "type": "git", - "url": "https://github.com/api-platform/core.git", - "reference": "f8dae8e1154480a49e86d2393118ffbd99acc51c" + "url": "https://github.com/api-platform/doctrine-common.git", + "reference": "8acbed7c2768f7c15a5b030018132e454f895e55" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/core/zipball/f8dae8e1154480a49e86d2393118ffbd99acc51c", - "reference": "f8dae8e1154480a49e86d2393118ffbd99acc51c", + "url": "https://api.github.com/repos/api-platform/doctrine-common/zipball/8acbed7c2768f7c15a5b030018132e454f895e55", + "reference": "8acbed7c2768f7c15a5b030018132e454f895e55", "shasum": "" }, "require": { - "doctrine/inflector": "^1.0 || ^2.0", - "php": ">=8.1", - "psr/cache": "^1.0 || ^2.0 || ^3.0", - "psr/container": "^1.0 || ^2.0", - "symfony/deprecation-contracts": "^3.1", - "symfony/http-foundation": "^6.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.1", - "willdurand/negotiation": "^3.0" + "api-platform/metadata": "^4.1.11", + "api-platform/state": "^4.1.11", + "doctrine/collections": "^2.1", + "doctrine/common": "^3.2.2", + "doctrine/persistence": "^3.2 || ^4.0", + "php": ">=8.2" }, "conflict": { - "doctrine/common": "<3.2.2", - "doctrine/dbal": "<2.10", - "doctrine/mongodb-odm": "<2.4", - "doctrine/orm": "<2.14.0", - "doctrine/persistence": "<1.3", - "elasticsearch/elasticsearch": ">=8.0,<8.4", - "phpspec/prophecy": "<1.15", - "phpunit/phpunit": "<9.5", - "symfony/framework-bundle": "6.4.6 || 7.0.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" + "doctrine/persistence": "<1.3" }, "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 || ^4.0", - "doctrine/doctrine-bundle": "^1.12 || ^2.0", - "doctrine/mongodb-odm": "^2.2", - "doctrine/mongodb-odm-bundle": "^4.0 || ^5.0", - "doctrine/orm": "^2.14 || ^3.0", - "elasticsearch/elasticsearch": "^7.11 || ^8.4", - "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.1", - "jangregor/phpstan-prophecy": "^1.0", - "justinrainbow/json-schema": "^5.2.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpstan/extension-installer": "^1.1", - "phpstan/phpdoc-parser": "^1.13|^2.0", - "phpstan/phpstan": "^1.10", - "phpstan/phpstan-doctrine": "^1.0", - "phpstan/phpstan-phpunit": "^1.0", - "phpstan/phpstan-symfony": "^1.0", - "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 || ^3.0", - "sebastian/comparator": "<5.0", - "soyuka/contexts": "v3.3.9", - "soyuka/pmu": "^0.0.12", - "soyuka/stubs-mongodb": "^1.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.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" + "doctrine/mongodb-odm": "^2.10", + "doctrine/orm": "^2.17 || ^3.0", + "phpspec/prophecy-phpunit": "^2.2", + "phpunit/phpunit": "11.5.x-dev", + "symfony/type-info": "^7.3" }, "suggest": { - "doctrine/mongodb-odm-bundle": "To support MongoDB. Only versions 4.0 and later are supported.", - "elasticsearch/elasticsearch": "To support Elasticsearch.", - "ocramius/package-versions": "To display the API Platform's version in the debug bar.", - "phpstan/phpdoc-parser": "To support extracting metadata from PHPDoc.", - "psr/cache-implementation": "To use metadata caching.", - "ramsey/uuid": "To support Ramsey's UUID identifiers.", - "symfony/cache": "To have metadata caching when using Symfony integration.", - "symfony/config": "To load XML configuration files.", - "symfony/expression-language": "To use authorization features.", - "symfony/http-client": "To use the HTTP cache invalidation system.", - "symfony/messenger": "To support messenger integration.", - "symfony/security": "To use authorization features.", - "symfony/twig-bundle": "To use the Swagger UI integration.", - "symfony/uid": "To support Symfony UUID/ULID identifiers.", - "symfony/web-profiler-bundle": "To use the data collector.", - "webonyx/graphql-php": "To support GraphQL." + "api-platform/graphql": "For GraphQl mercure subscriptions.", + "api-platform/http-cache": "For HTTP cache invalidation.", + "phpstan/phpdoc-parser": "For PHP documentation support.", + "symfony/config": "For XML resource configuration.", + "symfony/mercure-bundle": "For mercure updates publisher.", + "symfony/yaml": "For YAML resource configuration." }, "type": "library", "extra": { - "pmu": { - "projects": [ - "./src/*/composer.json", - "src/Doctrine/*/composer.json" - ] - }, "thanks": { "url": "https://github.com/api-platform/api-platform", "name": "api-platform/api-platform" }, "symfony": { - "require": "^6.4 || ^7.1" + "require": "^6.4 || ^7.0" }, "branch-alias": { "dev-3.4": "3.4.x-dev", - "dev-main": "4.0.x-dev" + "dev-4.1": "4.1.x-dev", + "dev-4.2": "4.2.x-dev", + "dev-main": "4.3.x-dev" } }, "autoload": { "psr-4": { - "ApiPlatform\\": "src/" + "ApiPlatform\\Doctrine\\Common\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -1163,251 +1036,312 @@ "name": "Kรฉvin Dunglas", "email": "kevin@dunglas.fr", "homepage": "https://dunglas.fr/" + }, + { + "name": "API Platform Community", + "homepage": "https://api-platform.com/community/contributors" } ], - "description": "Build a fully-featured hypermedia or GraphQL API in minutes!", + "description": "Common files used by api-platform/doctrine-orm and api-platform/doctrine-odm", "homepage": "https://api-platform.com/", "keywords": [ - "Hydra", - "JSON-LD", - "api", + "doctrine", "graphql", - "hal", - "jsonapi", - "openapi", - "rest", - "swagger" + "odm", + "orm", + "rest" ], "support": { - "issues": "https://github.com/api-platform/core/issues", - "source": "https://github.com/api-platform/core/tree/v3.4.10" + "source": "https://github.com/api-platform/doctrine-common/tree/v4.2.2" }, - "time": "2024-12-20T10:18:28+00:00" + "time": "2025-08-27T12:34:14+00:00" }, { - "name": "beberlei/assert", - "version": "v3.3.3", + "name": "api-platform/doctrine-orm", + "version": "v4.2.2", "source": { "type": "git", - "url": "https://github.com/beberlei/assert.git", - "reference": "b5fd8eacd8915a1b627b8bfc027803f1939734dd" + "url": "https://github.com/api-platform/doctrine-orm.git", + "reference": "d35d97423f7b399117ee033ecc886b3ed9dc2e23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/beberlei/assert/zipball/b5fd8eacd8915a1b627b8bfc027803f1939734dd", - "reference": "b5fd8eacd8915a1b627b8bfc027803f1939734dd", + "url": "https://api.github.com/repos/api-platform/doctrine-orm/zipball/d35d97423f7b399117ee033ecc886b3ed9dc2e23", + "reference": "d35d97423f7b399117ee033ecc886b3ed9dc2e23", "shasum": "" }, "require": { - "ext-ctype": "*", - "ext-json": "*", - "ext-mbstring": "*", - "ext-simplexml": "*", - "php": "^7.1 || ^8.0" + "api-platform/doctrine-common": "^4.2.0-alpha.3@alpha", + "api-platform/metadata": "^4.1.11", + "api-platform/state": "^4.1.11", + "doctrine/orm": "^2.17 || ^3.0", + "php": ">=8.2", + "symfony/type-info": "^7.3" }, "require-dev": { - "friendsofphp/php-cs-fixer": "*", - "phpstan/phpstan": "*", - "phpunit/phpunit": ">=6.0.0", - "yoast/phpunit-polyfills": "^0.1.0" - }, - "suggest": { - "ext-intl": "Needed to allow Assertion::count(), Assertion::isCountable(), Assertion::minCount(), and Assertion::maxCount() to operate on ResourceBundles" + "doctrine/doctrine-bundle": "^2.11", + "phpspec/prophecy-phpunit": "^2.2", + "phpunit/phpunit": "11.5.x-dev", + "ramsey/uuid": "^4.7", + "ramsey/uuid-doctrine": "^2.0", + "symfony/cache": "^6.4 || ^7.0", + "symfony/framework-bundle": "^6.4 || ^7.0", + "symfony/property-access": "^6.4 || ^7.0", + "symfony/property-info": "^6.4 || ^7.1", + "symfony/serializer": "^6.4 || ^7.0", + "symfony/uid": "^6.4 || ^7.0", + "symfony/validator": "^6.4 || ^7.0", + "symfony/yaml": "^6.4 || ^7.0" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/api-platform/api-platform", + "name": "api-platform/api-platform" + }, + "symfony": { + "require": "^6.4 || ^7.0" + }, + "branch-alias": { + "dev-3.4": "3.4.x-dev", + "dev-4.1": "4.1.x-dev", + "dev-4.2": "4.2.x-dev", + "dev-main": "4.3.x-dev" + } + }, "autoload": { - "files": [ - "lib/Assert/functions.php" - ], "psr-4": { - "Assert\\": "lib/Assert" + "ApiPlatform\\Doctrine\\Orm\\": "" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-2-Clause" + "MIT" ], "authors": [ { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de", - "role": "Lead Developer" + "name": "Kรฉvin Dunglas", + "email": "kevin@dunglas.fr", + "homepage": "https://dunglas.fr/" }, { - "name": "Richard Quadling", - "email": "rquadling@gmail.com", - "role": "Collaborator" + "name": "API Platform Community", + "homepage": "https://api-platform.com/community/contributors" } ], - "description": "Thin assertion library for input validation in business models.", + "description": "Doctrine ORM bridge", + "homepage": "https://api-platform.com/", "keywords": [ - "assert", - "assertion", - "validation" + "api", + "doctrine", + "graphql", + "orm", + "rest" ], "support": { - "issues": "https://github.com/beberlei/assert/issues", - "source": "https://github.com/beberlei/assert/tree/v3.3.3" + "source": "https://github.com/api-platform/doctrine-orm/tree/v4.2.2" }, - "time": "2024-07-15T13:18:35+00:00" + "time": "2025-10-07T13:54:25+00:00" }, { - "name": "beberlei/doctrineextensions", - "version": "v1.5.0", + "name": "api-platform/documentation", + "version": "v4.2.2", "source": { "type": "git", - "url": "https://github.com/beberlei/DoctrineExtensions.git", - "reference": "281f1650641c2f438b0a54d8eaa7ba50ac7e3eb6" + "url": "https://github.com/api-platform/documentation.git", + "reference": "c5a54336d8c51271aa5d54e57147cdee7162ab3a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/beberlei/DoctrineExtensions/zipball/281f1650641c2f438b0a54d8eaa7ba50ac7e3eb6", - "reference": "281f1650641c2f438b0a54d8eaa7ba50ac7e3eb6", + "url": "https://api.github.com/repos/api-platform/documentation/zipball/c5a54336d8c51271aa5d54e57147cdee7162ab3a", + "reference": "c5a54336d8c51271aa5d54e57147cdee7162ab3a", "shasum": "" }, "require": { - "doctrine/orm": "^2.19 || ^3.0", - "php": "^7.2 || ^8.0" + "api-platform/metadata": "^4.1.11", + "php": ">=8.2" }, "require-dev": { - "doctrine/annotations": "^1.14 || ^2", - "doctrine/coding-standard": "^9.0.2 || ^12.0", - "nesbot/carbon": "^2.72 || ^3", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^8.5 || ^9.6", - "squizlabs/php_codesniffer": "^3.8", - "symfony/cache": "^5.4 || ^6.4 || ^7.0", - "symfony/yaml": "^5.4 || ^6.4 || ^7.0", - "vimeo/psalm": "^3.18 || ^5.22", - "zf1/zend-date": "^1.12", - "zf1/zend-registry": "^1.12" + "phpunit/phpunit": "11.5.x-dev" + }, + "type": "project", + "extra": { + "thanks": { + "url": "https://github.com/api-platform/api-platform", + "name": "api-platform/api-platform" + }, + "symfony": { + "require": "^6.4 || ^7.0" + }, + "branch-alias": { + "dev-3.4": "3.4.x-dev", + "dev-4.1": "4.1.x-dev", + "dev-4.2": "4.2.x-dev", + "dev-main": "4.3.x-dev" + } }, - "type": "library", "autoload": { "psr-4": { - "DoctrineExtensions\\": "src/" + "ApiPlatform\\Documentation\\": "" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" + "name": "Kรฉvin Dunglas", + "email": "kevin@dunglas.fr", + "homepage": "https://dunglas.fr/" }, { - "name": "Steve Lacey", - "email": "steve@steve.ly" + "name": "API Platform Community", + "homepage": "https://api-platform.com/community/contributors" } ], - "description": "A set of extensions to Doctrine 2 that add support for additional query functions available in MySQL, Oracle, PostgreSQL and SQLite.", - "keywords": [ - "database", - "doctrine", - "orm" - ], + "description": "API Platform documentation controller.", "support": { - "source": "https://github.com/beberlei/DoctrineExtensions/tree/v1.5.0" + "source": "https://github.com/api-platform/documentation/tree/v4.2.2" }, - "time": "2024-03-03T17:55:15+00:00" + "time": "2025-08-19T08:04:29+00:00" }, { - "name": "brick/math", - "version": "0.12.1", + "name": "api-platform/http-cache", + "version": "v4.2.2", "source": { "type": "git", - "url": "https://github.com/brick/math.git", - "reference": "f510c0a40911935b77b86859eb5223d58d660df1" + "url": "https://github.com/api-platform/http-cache.git", + "reference": "aef434b026b861ea451d814c86838b5470b8bfb4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1", - "reference": "f510c0a40911935b77b86859eb5223d58d660df1", + "url": "https://api.github.com/repos/api-platform/http-cache/zipball/aef434b026b861ea451d814c86838b5470b8bfb4", + "reference": "aef434b026b861ea451d814c86838b5470b8bfb4", "shasum": "" }, "require": { - "php": "^8.1" + "api-platform/metadata": "^4.1.11", + "api-platform/state": "^4.1.11", + "php": ">=8.2", + "symfony/http-foundation": "^6.4 || ^7.0" }, "require-dev": { - "php-coveralls/php-coveralls": "^2.2", - "phpunit/phpunit": "^10.1", - "vimeo/psalm": "5.16.0" + "guzzlehttp/guzzle": "^6.0 || ^7.0", + "phpspec/prophecy-phpunit": "^2.2", + "phpunit/phpunit": "11.5.x-dev", + "symfony/dependency-injection": "^6.4 || ^7.0", + "symfony/http-client": "^6.4 || ^7.0", + "symfony/type-info": "^7.3" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/api-platform/api-platform", + "name": "api-platform/api-platform" + }, + "symfony": { + "require": "^6.4 || ^7.0" + }, + "branch-alias": { + "dev-3.4": "3.4.x-dev", + "dev-4.1": "4.1.x-dev", + "dev-4.2": "4.2.x-dev", + "dev-main": "4.3.x-dev" + } + }, "autoload": { "psr-4": { - "Brick\\Math\\": "src/" - } + "ApiPlatform\\HttpCache\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "Arbitrary-precision arithmetic library", + "authors": [ + { + "name": "Kรฉvin Dunglas", + "email": "kevin@dunglas.fr", + "homepage": "https://dunglas.fr/" + }, + { + "name": "API Platform Community", + "homepage": "https://api-platform.com/comunnity/contributors" + } + ], + "description": "API Platform HttpCache component", + "homepage": "https://api-platform.com/", "keywords": [ - "Arbitrary-precision", - "BigInteger", - "BigRational", - "arithmetic", - "bigdecimal", - "bignum", - "bignumber", - "brick", - "decimal", - "integer", - "math", - "mathematics", - "rational" + "api", + "cache", + "http", + "rest" ], "support": { - "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.12.1" + "source": "https://github.com/api-platform/http-cache/tree/v4.2.2" }, - "funding": [ - { - "url": "https://github.com/BenMorel", - "type": "github" - } - ], - "time": "2023-11-29T23:19:16+00:00" + "time": "2025-09-16T12:51:08+00:00" }, { - "name": "composer/ca-bundle", - "version": "1.5.4", + "name": "api-platform/hydra", + "version": "v4.2.2", "source": { "type": "git", - "url": "https://github.com/composer/ca-bundle.git", - "reference": "bc0593537a463e55cadf45fd938d23b75095b7e1" + "url": "https://github.com/api-platform/hydra.git", + "reference": "bfbe928e6a3999433ef11afc267e591152b17cc3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/bc0593537a463e55cadf45fd938d23b75095b7e1", - "reference": "bc0593537a463e55cadf45fd938d23b75095b7e1", + "url": "https://api.github.com/repos/api-platform/hydra/zipball/bfbe928e6a3999433ef11afc267e591152b17cc3", + "reference": "bfbe928e6a3999433ef11afc267e591152b17cc3", "shasum": "" }, "require": { - "ext-openssl": "*", - "ext-pcre": "*", - "php": "^7.2 || ^8.0" + "api-platform/documentation": "^4.1", + "api-platform/json-schema": "^4.2@beta", + "api-platform/jsonld": "^4.1", + "api-platform/metadata": "^4.2@beta", + "api-platform/serializer": "^4.1", + "api-platform/state": "^4.1.8", + "php": ">=8.2", + "symfony/type-info": "^7.3", + "symfony/web-link": "^6.4 || ^7.1" }, "require-dev": { - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^8 || ^9", - "psr/log": "^1.0 || ^2.0 || ^3.0", - "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "api-platform/doctrine-common": "^4.1", + "api-platform/doctrine-odm": "^4.1", + "api-platform/doctrine-orm": "^4.1", + "phpspec/prophecy": "^1.19", + "phpspec/prophecy-phpunit": "^2.2", + "phpunit/phpunit": "11.5.x-dev" }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/api-platform/api-platform", + "name": "api-platform/api-platform" + }, + "symfony": { + "require": "^6.4 || ^7.0" + }, "branch-alias": { - "dev-main": "1.x-dev" + "dev-3.4": "3.4.x-dev", + "dev-4.1": "4.1.x-dev", + "dev-4.2": "4.2.x-dev", + "dev-main": "4.3.x-dev" } }, "autoload": { "psr-4": { - "Composer\\CaBundle\\": "src" - } + "ApiPlatform\\Hydra\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1415,23 +1349,1103 @@ ], "authors": [ { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be/" + "name": "Kรฉvin Dunglas", + "email": "kevin@dunglas.fr", + "homepage": "https://dunglas.fr/" + }, + { + "name": "API Platform Community", + "homepage": "https://api-platform.com/community/contributors" } ], - "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "description": "API Hydra support", + "homepage": "https://api-platform.com/", "keywords": [ - "cabundle", - "cacert", - "certificate", - "ssl", - "tls" - ], - "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.4" + "Hydra", + "JSON-LD", + "api", + "graphql", + "jsonapi", + "rest" + ], + "support": { + "source": "https://github.com/api-platform/hydra/tree/v4.2.2" + }, + "time": "2025-10-07T13:39:38+00:00" + }, + { + "name": "api-platform/json-api", + "version": "v4.2.2", + "source": { + "type": "git", + "url": "https://github.com/api-platform/json-api.git", + "reference": "e8da698d55fb1702b25c63d7c821d1760159912e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/api-platform/json-api/zipball/e8da698d55fb1702b25c63d7c821d1760159912e", + "reference": "e8da698d55fb1702b25c63d7c821d1760159912e", + "shasum": "" + }, + "require": { + "api-platform/documentation": "^4.1.11", + "api-platform/json-schema": "^4.2@beta", + "api-platform/metadata": "^4.2@beta", + "api-platform/serializer": "^4.1.11", + "api-platform/state": "^4.1.11", + "php": ">=8.2", + "symfony/error-handler": "^6.4 || ^7.0", + "symfony/http-foundation": "^6.4 || ^7.0", + "symfony/type-info": "^7.3" + }, + "require-dev": { + "phpspec/prophecy": "^1.19", + "phpspec/prophecy-phpunit": "^2.2", + "phpunit/phpunit": "11.5.x-dev", + "symfony/type-info": "^7.3" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/api-platform/api-platform", + "name": "api-platform/api-platform" + }, + "symfony": { + "require": "^6.4 || ^7.0" + }, + "branch-alias": { + "dev-3.4": "3.4.x-dev", + "dev-4.1": "4.1.x-dev", + "dev-4.2": "4.2.x-dev", + "dev-main": "4.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "ApiPlatform\\JsonApi\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kรฉvin Dunglas", + "email": "kevin@dunglas.fr", + "homepage": "https://dunglas.fr/" + }, + { + "name": "API Platform Community", + "homepage": "https://api-platform.com/community/contributors" + } + ], + "description": "API JSON-API support", + "homepage": "https://api-platform.com/", + "keywords": [ + "api", + "jsonapi", + "rest" + ], + "support": { + "source": "https://github.com/api-platform/json-api/tree/v4.2.2" + }, + "time": "2025-09-16T12:49:22+00:00" + }, + { + "name": "api-platform/json-schema", + "version": "v4.2.2", + "source": { + "type": "git", + "url": "https://github.com/api-platform/json-schema.git", + "reference": "ec81bdd09375067d7d2555c10ec3dfc697874327" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/api-platform/json-schema/zipball/ec81bdd09375067d7d2555c10ec3dfc697874327", + "reference": "ec81bdd09375067d7d2555c10ec3dfc697874327", + "shasum": "" + }, + "require": { + "api-platform/metadata": "^4.2", + "php": ">=8.2", + "symfony/console": "^6.4 || ^7.0", + "symfony/property-info": "^6.4 || ^7.1", + "symfony/serializer": "^6.4 || ^7.0", + "symfony/type-info": "^7.3", + "symfony/uid": "^6.4 || ^7.0" + }, + "require-dev": { + "phpspec/prophecy-phpunit": "^2.2", + "phpunit/phpunit": "11.5.x-dev" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/api-platform/api-platform", + "name": "api-platform/api-platform" + }, + "symfony": { + "require": "^6.4 || ^7.0" + }, + "branch-alias": { + "dev-3.4": "3.4.x-dev", + "dev-4.1": "4.1.x-dev", + "dev-4.2": "4.2.x-dev", + "dev-main": "4.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "ApiPlatform\\JsonSchema\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kรฉvin Dunglas", + "email": "kevin@dunglas.fr", + "homepage": "https://dunglas.fr/" + }, + { + "name": "API Platform Community", + "homepage": "https://api-platform.com/community/contributors" + } + ], + "description": "Generate a JSON Schema from a PHP class", + "homepage": "https://api-platform.com/", + "keywords": [ + "JSON Schema", + "api", + "json", + "openapi", + "rest", + "swagger" + ], + "support": { + "source": "https://github.com/api-platform/json-schema/tree/v4.2.2" + }, + "time": "2025-10-07T09:45:59+00:00" + }, + { + "name": "api-platform/jsonld", + "version": "v4.2.2", + "source": { + "type": "git", + "url": "https://github.com/api-platform/jsonld.git", + "reference": "774b460273177983c52540a479ea9e9f940d7a1b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/api-platform/jsonld/zipball/774b460273177983c52540a479ea9e9f940d7a1b", + "reference": "774b460273177983c52540a479ea9e9f940d7a1b", + "shasum": "" + }, + "require": { + "api-platform/metadata": "^4.1.11", + "api-platform/serializer": "^4.1.11", + "api-platform/state": "^4.1.11", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "11.5.x-dev", + "symfony/type-info": "^7.3" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/api-platform/api-platform", + "name": "api-platform/api-platform" + }, + "symfony": { + "require": "^6.4 || ^7.0" + }, + "branch-alias": { + "dev-3.4": "3.4.x-dev", + "dev-4.1": "4.1.x-dev", + "dev-4.2": "4.2.x-dev", + "dev-main": "4.3.x-dev" + } + }, + "autoload": { + "files": [ + "./HydraContext.php" + ], + "psr-4": { + "ApiPlatform\\JsonLd\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kรฉvin Dunglas", + "email": "kevin@dunglas.fr", + "homepage": "https://dunglas.fr/" + }, + { + "name": "API Platform Community", + "homepage": "https://api-platform.com/community/contributors" + } + ], + "description": "API JSON-LD support", + "homepage": "https://api-platform.com/", + "keywords": [ + "Hydra", + "JSON-LD", + "api", + "graphql", + "rest" + ], + "support": { + "source": "https://github.com/api-platform/jsonld/tree/v4.2.2" + }, + "time": "2025-09-25T19:30:56+00:00" + }, + { + "name": "api-platform/metadata", + "version": "v4.2.2", + "source": { + "type": "git", + "url": "https://github.com/api-platform/metadata.git", + "reference": "5aeba910cb6352068664ca11cd9ee0dc208894c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/api-platform/metadata/zipball/5aeba910cb6352068664ca11cd9ee0dc208894c0", + "reference": "5aeba910cb6352068664ca11cd9ee0dc208894c0", + "shasum": "" + }, + "require": { + "doctrine/inflector": "^2.0", + "php": ">=8.2", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/property-info": "^6.4 || ^7.1", + "symfony/string": "^6.4 || ^7.0", + "symfony/type-info": "^7.3" + }, + "require-dev": { + "api-platform/json-schema": "^4.1.11", + "api-platform/openapi": "^4.1.11", + "api-platform/state": "^4.1.11", + "phpspec/prophecy-phpunit": "^2.2", + "phpstan/phpdoc-parser": "^1.29 || ^2.0", + "phpunit/phpunit": "11.5.x-dev", + "symfony/config": "^6.4 || ^7.0", + "symfony/routing": "^6.4 || ^7.0", + "symfony/var-dumper": "^6.4 || ^7.0", + "symfony/web-link": "^6.4 || ^7.1", + "symfony/yaml": "^6.4 || ^7.0" + }, + "suggest": { + "phpstan/phpdoc-parser": "For PHP documentation support.", + "symfony/config": "For XML resource configuration.", + "symfony/yaml": "For YAML resource configuration." + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/api-platform/api-platform", + "name": "api-platform/api-platform" + }, + "symfony": { + "require": "^6.4 || ^7.0" + }, + "branch-alias": { + "dev-3.4": "3.4.x-dev", + "dev-4.1": "4.1.x-dev", + "dev-4.2": "4.2.x-dev", + "dev-main": "4.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "ApiPlatform\\Metadata\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kรฉvin Dunglas", + "email": "kevin@dunglas.fr", + "homepage": "https://dunglas.fr/" + }, + { + "name": "API Platform Community", + "homepage": "https://api-platform.com/community/contributors" + } + ], + "description": "API Resource-oriented metadata attributes and factories", + "homepage": "https://api-platform.com/", + "keywords": [ + "Hydra", + "JSON-LD", + "api", + "graphql", + "hal", + "jsonapi", + "openapi", + "rest", + "swagger" + ], + "support": { + "source": "https://github.com/api-platform/metadata/tree/v4.2.2" + }, + "time": "2025-10-08T08:36:37+00:00" + }, + { + "name": "api-platform/openapi", + "version": "v4.2.2", + "source": { + "type": "git", + "url": "https://github.com/api-platform/openapi.git", + "reference": "2545f2be06eed0f9a121d631b8f1db22212a7826" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/api-platform/openapi/zipball/2545f2be06eed0f9a121d631b8f1db22212a7826", + "reference": "2545f2be06eed0f9a121d631b8f1db22212a7826", + "shasum": "" + }, + "require": { + "api-platform/json-schema": "^4.2@beta", + "api-platform/metadata": "^4.2@beta", + "api-platform/state": "^4.2@beta", + "php": ">=8.2", + "symfony/console": "^6.4 || ^7.0", + "symfony/filesystem": "^6.4 || ^7.0", + "symfony/property-access": "^6.4 || ^7.0", + "symfony/serializer": "^6.4 || ^7.0", + "symfony/type-info": "^7.3" + }, + "require-dev": { + "api-platform/doctrine-common": "^4.1", + "api-platform/doctrine-odm": "^4.1", + "api-platform/doctrine-orm": "^4.1", + "phpspec/prophecy-phpunit": "^2.2", + "phpunit/phpunit": "11.5.x-dev", + "symfony/type-info": "^7.3" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/api-platform/api-platform", + "name": "api-platform/api-platform" + }, + "symfony": { + "require": "^6.4 || ^7.0" + }, + "branch-alias": { + "dev-3.4": "3.4.x-dev", + "dev-4.1": "4.1.x-dev", + "dev-4.2": "4.2.x-dev", + "dev-main": "4.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "ApiPlatform\\OpenApi\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kรฉvin Dunglas", + "email": "kevin@dunglas.fr", + "homepage": "https://dunglas.fr/" + }, + { + "name": "API Platform Community", + "homepage": "https://api-platform.com/community/contributors" + } + ], + "description": "Models to build and serialize an OpenAPI specification.", + "homepage": "https://api-platform.com/", + "keywords": [ + "Hydra", + "JSON-LD", + "api", + "graphql", + "hal", + "jsonapi", + "openapi", + "rest", + "swagger" + ], + "support": { + "source": "https://github.com/api-platform/openapi/tree/v4.2.2" + }, + "time": "2025-09-30T12:06:50+00:00" + }, + { + "name": "api-platform/serializer", + "version": "v4.2.2", + "source": { + "type": "git", + "url": "https://github.com/api-platform/serializer.git", + "reference": "58c1378af6429049ee62951b838f6b645706c3a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/api-platform/serializer/zipball/58c1378af6429049ee62951b838f6b645706c3a3", + "reference": "58c1378af6429049ee62951b838f6b645706c3a3", + "shasum": "" + }, + "require": { + "api-platform/metadata": "^4.2.0", + "api-platform/state": "^4.1.11", + "php": ">=8.2", + "symfony/property-access": "^6.4 || ^7.0", + "symfony/property-info": "^6.4 || ^7.1", + "symfony/serializer": "^6.4 || ^7.0", + "symfony/validator": "^6.4 || ^7.0" + }, + "require-dev": { + "api-platform/doctrine-common": "^4.1", + "api-platform/doctrine-odm": "^4.1", + "api-platform/doctrine-orm": "^4.1", + "api-platform/json-schema": "^4.1", + "api-platform/openapi": "^4.1", + "doctrine/collections": "^2.1", + "phpspec/prophecy-phpunit": "^2.2", + "phpunit/phpunit": "11.5.x-dev", + "symfony/mercure-bundle": "*", + "symfony/type-info": "^7.3", + "symfony/var-dumper": "^6.4 || ^7.0", + "symfony/yaml": "^6.4 || ^7.0" + }, + "suggest": { + "api-platform/doctrine-odm": "To support Doctrine MongoDB ODM state options.", + "api-platform/doctrine-orm": "To support Doctrine ORM state options." + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/api-platform/api-platform", + "name": "api-platform/api-platform" + }, + "symfony": { + "require": "^6.4 || ^7.0" + }, + "branch-alias": { + "dev-3.4": "3.4.x-dev", + "dev-4.1": "4.1.x-dev", + "dev-4.2": "4.2.x-dev", + "dev-main": "4.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "ApiPlatform\\Serializer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kรฉvin Dunglas", + "email": "kevin@dunglas.fr", + "homepage": "https://dunglas.fr/" + }, + { + "name": "API Platform Community", + "homepage": "https://api-platform.com/community/contributors" + } + ], + "description": "API Platform core Serializer", + "homepage": "https://api-platform.com/", + "keywords": [ + "api", + "graphql", + "rest", + "serializer" + ], + "support": { + "source": "https://github.com/api-platform/serializer/tree/v4.2.2" + }, + "time": "2025-10-03T08:13:34+00:00" + }, + { + "name": "api-platform/state", + "version": "v4.2.2", + "source": { + "type": "git", + "url": "https://github.com/api-platform/state.git", + "reference": "7dc3dfbafa4627cc789bd8bcd4da46e6c37f6b26" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/api-platform/state/zipball/7dc3dfbafa4627cc789bd8bcd4da46e6c37f6b26", + "reference": "7dc3dfbafa4627cc789bd8bcd4da46e6c37f6b26", + "shasum": "" + }, + "require": { + "api-platform/metadata": "^4.1.18", + "php": ">=8.2", + "psr/container": "^1.0 || ^2.0", + "symfony/http-kernel": "^6.4 || ^7.0", + "symfony/serializer": "^6.4 || ^7.0", + "symfony/translation-contracts": "^3.0" + }, + "require-dev": { + "api-platform/serializer": "^4.1", + "api-platform/validator": "^4.1", + "phpunit/phpunit": "11.5.x-dev", + "symfony/http-foundation": "^6.4 || ^7.0", + "symfony/object-mapper": "^7.3", + "symfony/type-info": "^7.3", + "symfony/web-link": "^6.4 || ^7.1", + "willdurand/negotiation": "^3.1" + }, + "suggest": { + "api-platform/serializer": "To use API Platform serializer.", + "api-platform/validator": "To use API Platform validation.", + "symfony/http-foundation": "To use our HTTP providers and processor.", + "symfony/web-link": "To support adding web links to the response headers.", + "willdurand/negotiation": "To use the API Platform content negoatiation provider." + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/api-platform/api-platform", + "name": "api-platform/api-platform" + }, + "symfony": { + "require": "^6.4 || ^7.0" + }, + "branch-alias": { + "dev-3.4": "3.4.x-dev", + "dev-4.1": "4.1.x-dev", + "dev-4.2": "4.2.x-dev", + "dev-main": "4.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "ApiPlatform\\State\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kรฉvin Dunglas", + "email": "kevin@dunglas.fr", + "homepage": "https://dunglas.fr/" + }, + { + "name": "API Platform Community", + "homepage": "https://api-platform.com/community/contributors" + } + ], + "description": "API Platform State component ", + "homepage": "https://api-platform.com/", + "keywords": [ + "Hydra", + "JSON-LD", + "api", + "graphql", + "hal", + "jsonapi", + "openapi", + "rest", + "swagger" + ], + "support": { + "source": "https://github.com/api-platform/state/tree/v4.2.2" + }, + "time": "2025-10-09T08:33:56+00:00" + }, + { + "name": "api-platform/symfony", + "version": "v4.2.2", + "source": { + "type": "git", + "url": "https://github.com/api-platform/symfony.git", + "reference": "44bb117df1cd5695203ec6a97999cabc85257780" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/api-platform/symfony/zipball/44bb117df1cd5695203ec6a97999cabc85257780", + "reference": "44bb117df1cd5695203ec6a97999cabc85257780", + "shasum": "" + }, + "require": { + "api-platform/documentation": "^4.1.11", + "api-platform/http-cache": "^4.1.11", + "api-platform/hydra": "^4.1.11", + "api-platform/json-schema": "^4.1.11", + "api-platform/jsonld": "^4.1.11", + "api-platform/metadata": "^4.2@beta", + "api-platform/openapi": "^4.1.11", + "api-platform/serializer": "^4.1.11", + "api-platform/state": "^4.2@beta", + "api-platform/validator": "^4.1.11", + "php": ">=8.2", + "symfony/finder": "^6.4 || ^7.0", + "symfony/property-access": "^6.4 || ^7.0", + "symfony/property-info": "^6.4 || ^7.1", + "symfony/security-core": "^6.4 || ^7.0", + "symfony/serializer": "^6.4 || ^7.0", + "willdurand/negotiation": "^3.1" + }, + "require-dev": { + "api-platform/doctrine-common": "^4.1", + "api-platform/doctrine-odm": "^4.1", + "api-platform/doctrine-orm": "^4.1", + "api-platform/elasticsearch": "^4.1", + "api-platform/graphql": "^4.1", + "api-platform/parameter-validator": "^3.1", + "phpspec/prophecy-phpunit": "^2.2", + "phpunit/phpunit": "11.5.x-dev", + "symfony/expression-language": "^6.4 || ^7.0", + "symfony/intl": "^6.4 || ^7.0", + "symfony/mercure-bundle": "*", + "symfony/object-mapper": "^7.0", + "symfony/routing": "^6.4 || ^7.0", + "symfony/type-info": "^7.3", + "symfony/validator": "^6.4 || ^7.0", + "webonyx/graphql-php": "^15.0" + }, + "suggest": { + "api-platform/doctrine-odm": "To support MongoDB. Only versions 4.0 and later are supported.", + "api-platform/doctrine-orm": "To support Doctrine ORM.", + "api-platform/elasticsearch": "To support Elasticsearch.", + "api-platform/graphql": "To support GraphQL.", + "api-platform/hal": "to support the HAL format", + "api-platform/ramsey-uuid": "To support Ramsey's UUID identifiers.", + "phpstan/phpdoc-parser": "To support extracting metadata from PHPDoc.", + "psr/cache-implementation": "To use metadata caching.", + "symfony/cache": "To have metadata caching when using Symfony integration.", + "symfony/config": "To load XML configuration files.", + "symfony/expression-language": "To use authorization and mercure advanced features.", + "symfony/http-client": "To use the HTTP cache invalidation system.", + "symfony/mercure-bundle": "To support mercure integration.", + "symfony/messenger": "To support messenger integration and asynchronous Mercure updates.", + "symfony/security": "To use authorization features.", + "symfony/twig-bundle": "To use the Swagger UI integration.", + "symfony/uid": "To support Symfony UUID/ULID identifiers.", + "symfony/web-profiler-bundle": "To use the data collector." + }, + "type": "symfony-bundle", + "extra": { + "thanks": { + "url": "https://github.com/api-platform/api-platform", + "name": "api-platform/api-platform" + }, + "symfony": { + "require": "^6.4 || ^7.0" + }, + "branch-alias": { + "dev-3.4": "3.4.x-dev", + "dev-4.1": "4.1.x-dev", + "dev-4.2": "4.2.x-dev", + "dev-main": "4.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "ApiPlatform\\Symfony\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kรฉvin Dunglas", + "email": "kevin@dunglas.fr", + "homepage": "https://dunglas.fr/" + }, + { + "name": "API Platform Community", + "homepage": "https://api-platform.com/community/contributors" + } + ], + "description": "Symfony API Platform integration", + "homepage": "https://api-platform.com/", + "keywords": [ + "Hydra", + "JSON-LD", + "api", + "graphql", + "hal", + "jsonapi", + "openapi", + "rest", + "swagger", + "symfony" + ], + "support": { + "source": "https://github.com/api-platform/symfony/tree/v4.2.2" + }, + "time": "2025-10-09T08:33:56+00:00" + }, + { + "name": "api-platform/validator", + "version": "v4.2.2", + "source": { + "type": "git", + "url": "https://github.com/api-platform/validator.git", + "reference": "9986e84b27e3de7f87c7c23f238430a572f87f67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/api-platform/validator/zipball/9986e84b27e3de7f87c7c23f238430a572f87f67", + "reference": "9986e84b27e3de7f87c7c23f238430a572f87f67", + "shasum": "" + }, + "require": { + "api-platform/metadata": "^4.1.11", + "php": ">=8.2", + "symfony/http-kernel": "^6.4 || ^7.1", + "symfony/serializer": "^6.4 || ^7.1", + "symfony/type-info": "^7.3", + "symfony/validator": "^6.4 || ^7.1", + "symfony/web-link": "^6.4 || ^7.1" + }, + "require-dev": { + "phpspec/prophecy-phpunit": "^2.2", + "phpunit/phpunit": "11.5.x-dev" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/api-platform/api-platform", + "name": "api-platform/api-platform" + }, + "symfony": { + "require": "^6.4 || ^7.0" + }, + "branch-alias": { + "dev-3.4": "3.4.x-dev", + "dev-4.1": "4.1.x-dev", + "dev-4.2": "4.2.x-dev", + "dev-main": "4.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "ApiPlatform\\Validator\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kรฉvin Dunglas", + "email": "kevin@dunglas.fr", + "homepage": "https://dunglas.fr/" + }, + { + "name": "API Platform Community", + "homepage": "https://api-platform.com/community/contributors" + } + ], + "description": "API Platform validator component", + "homepage": "https://api-platform.com/", + "keywords": [ + "api", + "graphql", + "rest", + "validator" + ], + "support": { + "source": "https://github.com/api-platform/validator/tree/v4.2.2" + }, + "time": "2025-09-29T15:36:04+00:00" + }, + { + "name": "beberlei/assert", + "version": "v3.3.3", + "source": { + "type": "git", + "url": "https://github.com/beberlei/assert.git", + "reference": "b5fd8eacd8915a1b627b8bfc027803f1939734dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/beberlei/assert/zipball/b5fd8eacd8915a1b627b8bfc027803f1939734dd", + "reference": "b5fd8eacd8915a1b627b8bfc027803f1939734dd", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "*", + "phpstan/phpstan": "*", + "phpunit/phpunit": ">=6.0.0", + "yoast/phpunit-polyfills": "^0.1.0" + }, + "suggest": { + "ext-intl": "Needed to allow Assertion::count(), Assertion::isCountable(), Assertion::minCount(), and Assertion::maxCount() to operate on ResourceBundles" + }, + "type": "library", + "autoload": { + "files": [ + "lib/Assert/functions.php" + ], + "psr-4": { + "Assert\\": "lib/Assert" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de", + "role": "Lead Developer" + }, + { + "name": "Richard Quadling", + "email": "rquadling@gmail.com", + "role": "Collaborator" + } + ], + "description": "Thin assertion library for input validation in business models.", + "keywords": [ + "assert", + "assertion", + "validation" + ], + "support": { + "issues": "https://github.com/beberlei/assert/issues", + "source": "https://github.com/beberlei/assert/tree/v3.3.3" + }, + "time": "2024-07-15T13:18:35+00:00" + }, + { + "name": "beberlei/doctrineextensions", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/beberlei/DoctrineExtensions.git", + "reference": "281f1650641c2f438b0a54d8eaa7ba50ac7e3eb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/beberlei/DoctrineExtensions/zipball/281f1650641c2f438b0a54d8eaa7ba50ac7e3eb6", + "reference": "281f1650641c2f438b0a54d8eaa7ba50ac7e3eb6", + "shasum": "" + }, + "require": { + "doctrine/orm": "^2.19 || ^3.0", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^1.14 || ^2", + "doctrine/coding-standard": "^9.0.2 || ^12.0", + "nesbot/carbon": "^2.72 || ^3", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8.5 || ^9.6", + "squizlabs/php_codesniffer": "^3.8", + "symfony/cache": "^5.4 || ^6.4 || ^7.0", + "symfony/yaml": "^5.4 || ^6.4 || ^7.0", + "vimeo/psalm": "^3.18 || ^5.22", + "zf1/zend-date": "^1.12", + "zf1/zend-registry": "^1.12" + }, + "type": "library", + "autoload": { + "psr-4": { + "DoctrineExtensions\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Steve Lacey", + "email": "steve@steve.ly" + } + ], + "description": "A set of extensions to Doctrine 2 that add support for additional query functions available in MySQL, Oracle, PostgreSQL and SQLite.", + "keywords": [ + "database", + "doctrine", + "orm" + ], + "support": { + "source": "https://github.com/beberlei/DoctrineExtensions/tree/v1.5.0" + }, + "time": "2024-03-03T17:55:15+00:00" + }, + { + "name": "brick/math", + "version": "0.13.1", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/fc7ed316430118cc7836bf45faff18d5dfc8de04", + "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^10.1", + "vimeo/psalm": "6.8.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.13.1" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + } + ], + "time": "2025-03-29T13:50:30+00:00" + }, + { + "name": "composer/ca-bundle", + "version": "1.5.8", + "source": { + "type": "git", + "url": "https://github.com/composer/ca-bundle.git", + "reference": "719026bb30813accb68271fee7e39552a58e9f65" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/719026bb30813accb68271fee7e39552a58e9f65", + "reference": "719026bb30813accb68271fee7e39552a58e9f65", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8 || ^9", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\CaBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be/" + } + ], + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "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.8" }, "funding": [ { @@ -1441,13 +2455,9 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2024-11-27T15:35:25+00:00" + "time": "2025-08-20T18:49:47+00:00" }, { "name": "composer/package-versions-deprecated", @@ -1522,6 +2532,85 @@ ], "time": "2022-01-17T14:14:24+00:00" }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be/" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com/", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, { "name": "daverandom/libdns", "version": "v2.1.0", @@ -1567,37 +2656,110 @@ "time": "2024-04-12T12:12:48+00:00" }, { - "name": "doctrine/cache", - "version": "2.2.0", + "name": "dflydev/dot-access-data", + "version": "v3.0.3", "source": { "type": "git", - "url": "https://github.com/doctrine/cache.git", - "reference": "1ca8f21980e770095a31456042471a57bc4c68fb" + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/1ca8f21980e770095a31456042471a57bc4c68fb", - "reference": "1ca8f21980e770095a31456042471a57bc4c68fb", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f", "shasum": "" }, "require": { - "php": "~7.1 || ^8.0" + "php": "^7.1 || ^8.0" }, - "conflict": { - "doctrine/common": ">2.2,<2.4" + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com/" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com/" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com/" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3" + }, + "time": "2024-07-08T12:26:09+00:00" + }, + { + "name": "doctrine/collections", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "2eb07e5953eed811ce1b309a7478a3b236f2273d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/2eb07e5953eed811ce1b309a7478a3b236f2273d", + "reference": "2eb07e5953eed811ce1b309a7478a3b236f2273d", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1", + "php": "^8.1", + "symfony/polyfill-php84": "^1.30" }, "require-dev": { - "cache/integration-tests": "dev-master", - "doctrine/coding-standard": "^9", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psr/cache": "^1.0 || ^2.0 || ^3.0", - "symfony/cache": "^4.4 || ^5.4 || ^6", - "symfony/var-exporter": "^4.4 || ^5.4 || ^6" + "doctrine/coding-standard": "^12", + "ext-json": "*", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^10.5" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + "Doctrine\\Common\\Collections\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1626,22 +2788,17 @@ "email": "schmittjoh@gmail.com" } ], - "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", - "homepage": "https://www.doctrine-project.org/projects/cache.html", + "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", + "homepage": "https://www.doctrine-project.org/projects/collections.html", "keywords": [ - "abstraction", - "apcu", - "cache", - "caching", - "couchdb", - "memcached", - "php", - "redis", - "xcache" + "array", + "collections", + "iterators", + "php" ], "support": { - "issues": "https://github.com/doctrine/cache/issues", - "source": "https://github.com/doctrine/cache/tree/2.2.0" + "issues": "https://github.com/doctrine/collections/issues", + "source": "https://github.com/doctrine/collections/tree/2.3.0" }, "funding": [ { @@ -1653,42 +2810,44 @@ "type": "patreon" }, { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcollections", "type": "tidelift" } ], - "time": "2022-05-20T20:07:39+00:00" + "time": "2025-03-22T10:17:19+00:00" }, { - "name": "doctrine/collections", - "version": "2.2.2", + "name": "doctrine/common", + "version": "3.5.0", "source": { "type": "git", - "url": "https://github.com/doctrine/collections.git", - "reference": "d8af7f248c74f195f7347424600fd9e17b57af59" + "url": "https://github.com/doctrine/common.git", + "reference": "d9ea4a54ca2586db781f0265d36bea731ac66ec5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/d8af7f248c74f195f7347424600fd9e17b57af59", - "reference": "d8af7f248c74f195f7347424600fd9e17b57af59", + "url": "https://api.github.com/repos/doctrine/common/zipball/d9ea4a54ca2586db781f0265d36bea731ac66ec5", + "reference": "d9ea4a54ca2586db781f0265d36bea731ac66ec5", "shasum": "" }, "require": { - "doctrine/deprecations": "^1", - "php": "^8.1" + "doctrine/persistence": "^2.0 || ^3.0 || ^4.0", + "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^12", - "ext-json": "*", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^10.5", - "vimeo/psalm": "^5.11" + "doctrine/coding-standard": "^9.0 || ^10.0", + "doctrine/collections": "^1", + "phpstan/phpstan": "^1.4.1", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.0", + "squizlabs/php_codesniffer": "^3.0", + "symfony/phpunit-bridge": "^6.1", + "vimeo/psalm": "^4.4" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Common\\Collections\\": "src" + "Doctrine\\Common\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1715,19 +2874,22 @@ { "name": "Johannes Schmitt", "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" } ], - "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", - "homepage": "https://www.doctrine-project.org/projects/collections.html", + "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, proxies and much more.", + "homepage": "https://www.doctrine-project.org/projects/common.html", "keywords": [ - "array", - "collections", - "iterators", + "common", + "doctrine", "php" ], "support": { - "issues": "https://github.com/doctrine/collections/issues", - "source": "https://github.com/doctrine/collections/tree/2.2.2" + "issues": "https://github.com/doctrine/common/issues", + "source": "https://github.com/doctrine/common/tree/3.5.0" }, "funding": [ { @@ -1739,28 +2901,28 @@ "type": "patreon" }, { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcollections", + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcommon", "type": "tidelift" } ], - "time": "2024-04-18T06:56:21+00:00" + "time": "2025-01-01T22:12:03+00:00" }, { "name": "doctrine/data-fixtures", - "version": "2.0.1", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/data-fixtures.git", - "reference": "2ae45139f148c9272c49a521d82cc50491355a99" + "reference": "7a615ba135e45d67674bb623d90f34f6c7b6bd97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/2ae45139f148c9272c49a521d82cc50491355a99", - "reference": "2ae45139f148c9272c49a521d82cc50491355a99", + "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/7a615ba135e45d67674bb623d90f34f6c7b6bd97", + "reference": "7a615ba135e45d67674bb623d90f34f6c7b6bd97", "shasum": "" }, "require": { - "doctrine/persistence": "^3.1", + "doctrine/persistence": "^3.1 || ^4.0", "php": "^8.1", "psr/log": "^1.1 || ^2 || ^3" }, @@ -1770,14 +2932,14 @@ "doctrine/phpcr-odm": "<1.3.0" }, "require-dev": { - "doctrine/coding-standard": "^12", + "doctrine/coding-standard": "^14", "doctrine/dbal": "^3.5 || ^4", "doctrine/mongodb-odm": "^1.3.0 || ^2.0.0", "doctrine/orm": "^2.14 || ^3", "ext-sqlite3": "*", "fig/log-test": "^1", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^10.5.3", + "phpstan/phpstan": "2.1.31", + "phpunit/phpunit": "10.5.45 || 12.4.0", "symfony/cache": "^6.4 || ^7", "symfony/var-exporter": "^6.4 || ^7" }, @@ -1810,7 +2972,7 @@ ], "support": { "issues": "https://github.com/doctrine/data-fixtures/issues", - "source": "https://github.com/doctrine/data-fixtures/tree/2.0.1" + "source": "https://github.com/doctrine/data-fixtures/tree/2.2.0" }, "funding": [ { @@ -1826,42 +2988,40 @@ "type": "tidelift" } ], - "time": "2024-12-10T07:03:23+00:00" + "time": "2025-10-17T20:06:20+00:00" }, { "name": "doctrine/dbal", - "version": "4.2.1", + "version": "4.3.4", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "dadd35300837a3a2184bd47d403333b15d0a9bd0" + "reference": "1a2fbd0e93b8dec7c3d1ac2b6396a7b929b130dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/dadd35300837a3a2184bd47d403333b15d0a9bd0", - "reference": "dadd35300837a3a2184bd47d403333b15d0a9bd0", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/1a2fbd0e93b8dec7c3d1ac2b6396a7b929b130dc", + "reference": "1a2fbd0e93b8dec7c3d1ac2b6396a7b929b130dc", "shasum": "" }, "require": { - "doctrine/deprecations": "^0.5.3|^1", - "php": "^8.1", + "doctrine/deprecations": "^1.1.5", + "php": "^8.2", "psr/cache": "^1|^2|^3", "psr/log": "^1|^2|^3" }, "require-dev": { - "doctrine/coding-standard": "12.0.0", + "doctrine/coding-standard": "14.0.0", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2023.2", - "phpstan/phpstan": "1.12.6", - "phpstan/phpstan-phpunit": "1.4.0", - "phpstan/phpstan-strict-rules": "^1.6", - "phpunit/phpunit": "10.5.30", - "psalm/plugin-phpunit": "0.19.0", - "slevomat/coding-standard": "8.13.1", - "squizlabs/php_codesniffer": "3.10.2", + "phpstan/phpstan": "2.1.30", + "phpstan/phpstan-phpunit": "2.0.7", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "11.5.23", + "slevomat/coding-standard": "8.24.0", + "squizlabs/php_codesniffer": "4.0.0", "symfony/cache": "^6.3.8|^7.0", - "symfony/console": "^5.4|^6.3|^7.0", - "vimeo/psalm": "5.25.0" + "symfony/console": "^5.4|^6.3|^7.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -1918,7 +3078,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/4.2.1" + "source": "https://github.com/doctrine/dbal/tree/4.3.4" }, "funding": [ { @@ -1934,30 +3094,33 @@ "type": "tidelift" } ], - "time": "2024-10-10T18:01:27+00:00" + "time": "2025-10-09T09:11:36+00:00" }, { "name": "doctrine/deprecations", - "version": "1.1.4", + "version": "1.1.5", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9" + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/31610dbb31faa98e6b5447b62340826f54fbc4e9", - "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=13" + }, "require-dev": { - "doctrine/coding-standard": "^9 || ^12", - "phpstan/phpstan": "1.4.10 || 2.0.3", + "doctrine/coding-standard": "^9 || ^12 || ^13", + "phpstan/phpstan": "1.4.10 || 2.1.11", "phpstan/phpstan-phpunit": "^1.0 || ^2", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12", "psr/log": "^1 || ^2 || ^3" }, "suggest": { @@ -1977,68 +3140,69 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.4" + "source": "https://github.com/doctrine/deprecations/tree/1.1.5" }, - "time": "2024-12-07T21:18:45+00:00" + "time": "2025-04-07T20:06:18+00:00" }, { "name": "doctrine/doctrine-bundle", - "version": "2.13.1", + "version": "2.18.0", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineBundle.git", - "reference": "2740ad8b8739b39ab37d409c972b092f632b025a" + "reference": "cd5d4da6a5f7cf3d8708e17211234657b5eb4e95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/2740ad8b8739b39ab37d409c972b092f632b025a", - "reference": "2740ad8b8739b39ab37d409c972b092f632b025a", + "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/cd5d4da6a5f7cf3d8708e17211234657b5eb4e95", + "reference": "cd5d4da6a5f7cf3d8708e17211234657b5eb4e95", "shasum": "" }, "require": { - "doctrine/cache": "^1.11 || ^2.0", "doctrine/dbal": "^3.7.0 || ^4.0", - "doctrine/persistence": "^2.2 || ^3", + "doctrine/deprecations": "^1.0", + "doctrine/persistence": "^3.1 || ^4", "doctrine/sql-formatter": "^1.0.1", - "php": "^7.4 || ^8.0", - "symfony/cache": "^5.4 || ^6.0 || ^7.0", - "symfony/config": "^5.4 || ^6.0 || ^7.0", - "symfony/console": "^5.4 || ^6.0 || ^7.0", - "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", - "symfony/deprecation-contracts": "^2.1 || ^3", - "symfony/doctrine-bridge": "^5.4.46 || ^6.4.3 || ^7.0.3", - "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0", - "symfony/polyfill-php80": "^1.15", - "symfony/service-contracts": "^1.1.1 || ^2.0 || ^3" + "php": "^8.1", + "symfony/cache": "^6.4 || ^7.0", + "symfony/config": "^6.4 || ^7.0", + "symfony/console": "^6.4 || ^7.0", + "symfony/dependency-injection": "^6.4 || ^7.0", + "symfony/doctrine-bridge": "^6.4.3 || ^7.0.3", + "symfony/framework-bundle": "^6.4 || ^7.0", + "symfony/service-contracts": "^2.5 || ^3" }, "conflict": { "doctrine/annotations": ">=3.0", + "doctrine/cache": "< 1.11", "doctrine/orm": "<2.17 || >=4.0", - "twig/twig": "<1.34 || >=2.0 <2.4" + "symfony/var-exporter": "< 6.4.1 || 7.0.0", + "twig/twig": "<2.13 || >=3.0 <3.0.4" }, "require-dev": { "doctrine/annotations": "^1 || ^2", - "doctrine/coding-standard": "^12", - "doctrine/deprecations": "^1.0", - "doctrine/orm": "^2.17 || ^3.0", + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/coding-standard": "^14", + "doctrine/orm": "^2.17 || ^3.1", "friendsofphp/proxy-manager-lts": "^1.0", - "phpunit/phpunit": "^9.5.26", - "psalm/plugin-phpunit": "^0.18.4", - "psalm/plugin-symfony": "^5", + "phpstan/phpstan": "2.1.1", + "phpstan/phpstan-phpunit": "2.0.3", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.53 || ^12.3.10", "psr/log": "^1.1.4 || ^2.0 || ^3.0", - "symfony/phpunit-bridge": "^6.1 || ^7.0", - "symfony/property-info": "^5.4 || ^6.0 || ^7.0", - "symfony/proxy-manager-bridge": "^5.4 || ^6.0 || ^7.0", - "symfony/security-bundle": "^5.4 || ^6.0 || ^7.0", - "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0", - "symfony/string": "^5.4 || ^6.0 || ^7.0", - "symfony/twig-bridge": "^5.4 || ^6.0 || ^7.0", - "symfony/validator": "^5.4 || ^6.0 || ^7.0", - "symfony/var-exporter": "^5.4 || ^6.2 || ^7.0", - "symfony/web-profiler-bundle": "^5.4 || ^6.0 || ^7.0", - "symfony/yaml": "^5.4 || ^6.0 || ^7.0", - "twig/twig": "^1.34 || ^2.12 || ^3.0", - "vimeo/psalm": "^5.15" + "symfony/doctrine-messenger": "^6.4 || ^7.0", + "symfony/expression-language": "^6.4 || ^7.0", + "symfony/messenger": "^6.4 || ^7.0", + "symfony/property-info": "^6.4 || ^7.0", + "symfony/security-bundle": "^6.4 || ^7.0", + "symfony/stopwatch": "^6.4 || ^7.0", + "symfony/string": "^6.4 || ^7.0", + "symfony/twig-bridge": "^6.4 || ^7.0", + "symfony/validator": "^6.4 || ^7.0", + "symfony/var-exporter": "^6.4.1 || ^7.0.1", + "symfony/web-profiler-bundle": "^6.4 || ^7.0", + "symfony/yaml": "^6.4 || ^7.0", + "twig/twig": "^2.14.7 || ^3.0.4" }, "suggest": { "doctrine/orm": "The Doctrine ORM integration is optional in the bundle.", @@ -2083,7 +3247,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineBundle/issues", - "source": "https://github.com/doctrine/DoctrineBundle/tree/2.13.1" + "source": "https://github.com/doctrine/DoctrineBundle/tree/2.18.0" }, "funding": [ { @@ -2099,54 +3263,47 @@ "type": "tidelift" } ], - "time": "2024-11-08T23:27:54+00:00" + "time": "2025-10-11T04:43:27+00:00" }, { "name": "doctrine/doctrine-migrations-bundle", - "version": "3.3.1", + "version": "3.5.0", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineMigrationsBundle.git", - "reference": "715b62c31a5894afcb2b2cdbbc6607d7dd0580c0" + "reference": "71c81279ca0e907c3edc718418b93fd63074856c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/715b62c31a5894afcb2b2cdbbc6607d7dd0580c0", - "reference": "715b62c31a5894afcb2b2cdbbc6607d7dd0580c0", + "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/71c81279ca0e907c3edc718418b93fd63074856c", + "reference": "71c81279ca0e907c3edc718418b93fd63074856c", "shasum": "" }, "require": { - "doctrine/doctrine-bundle": "^2.4", + "doctrine/doctrine-bundle": "^2.4 || ^3.0", "doctrine/migrations": "^3.2", - "php": "^7.2|^8.0", + "php": "^7.2 || ^8.0", "symfony/deprecation-contracts": "^2.1 || ^3", "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0" }, "require-dev": { "composer/semver": "^3.0", - "doctrine/coding-standard": "^12", + "doctrine/coding-standard": "^12 || ^14", "doctrine/orm": "^2.6 || ^3", - "doctrine/persistence": "^2.0 || ^3 ", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-deprecation-rules": "^1", - "phpstan/phpstan-phpunit": "^1", - "phpstan/phpstan-strict-rules": "^1.1", - "phpstan/phpstan-symfony": "^1.3", - "phpunit/phpunit": "^8.5|^9.5", - "psalm/plugin-phpunit": "^0.18.4", - "psalm/plugin-symfony": "^3 || ^5", + "phpstan/phpstan": "^1.4 || ^2", + "phpstan/phpstan-deprecation-rules": "^1 || ^2", + "phpstan/phpstan-phpunit": "^1 || ^2", + "phpstan/phpstan-strict-rules": "^1.1 || ^2", + "phpstan/phpstan-symfony": "^1.3 || ^2", + "phpunit/phpunit": "^8.5 || ^9.5", "symfony/phpunit-bridge": "^6.3 || ^7", - "symfony/var-exporter": "^5.4 || ^6 || ^7", - "vimeo/psalm": "^4.30 || ^5.15" + "symfony/var-exporter": "^5.4 || ^6 || ^7" }, "type": "symfony-bundle", "autoload": { "psr-4": { - "Doctrine\\Bundle\\MigrationsBundle\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Doctrine\\Bundle\\MigrationsBundle\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2175,7 +3332,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineMigrationsBundle/issues", - "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/3.3.1" + "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/3.5.0" }, "funding": [ { @@ -2191,7 +3348,7 @@ "type": "tidelift" } ], - "time": "2024-05-14T20:32:18+00:00" + "time": "2025-10-12T17:06:40+00:00" }, { "name": "doctrine/event-manager", @@ -2286,33 +3443,32 @@ }, { "name": "doctrine/inflector", - "version": "2.0.10", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc" + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/5817d0659c5b50c9b950feb9af7b9668e2c436bc", - "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^11.0", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-strict-rules": "^1.3", - "phpunit/phpunit": "^8.5 || ^9.5", - "vimeo/psalm": "^4.25 || ^5.4" + "doctrine/coding-standard": "^12.0 || ^13.0", + "phpstan/phpstan": "^1.12 || ^2.0", + "phpstan/phpstan-phpunit": "^1.4 || ^2.0", + "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", + "phpunit/phpunit": "^8.5 || ^12.2" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + "Doctrine\\Inflector\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -2357,7 +3513,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.10" + "source": "https://github.com/doctrine/inflector/tree/2.1.0" }, "funding": [ { @@ -2373,7 +3529,7 @@ "type": "tidelift" } ], - "time": "2024-02-18T20:23:39+00:00" + "time": "2025-08-10T19:31:58+00:00" }, { "name": "doctrine/instantiator", @@ -2524,16 +3680,16 @@ }, { "name": "doctrine/migrations", - "version": "3.8.2", + "version": "3.9.4", "source": { "type": "git", "url": "https://github.com/doctrine/migrations.git", - "reference": "5007eb1168691225ac305fe16856755c20860842" + "reference": "1b88fcb812f2cd6e77c83d16db60e3cf1e35c66c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/migrations/zipball/5007eb1168691225ac305fe16856755c20860842", - "reference": "5007eb1168691225ac305fe16856755c20860842", + "url": "https://api.github.com/repos/doctrine/migrations/zipball/1b88fcb812f2cd6e77c83d16db60e3cf1e35c66c", + "reference": "1b88fcb812f2cd6e77c83d16db60e3cf1e35c66c", "shasum": "" }, "require": { @@ -2551,18 +3707,18 @@ "doctrine/orm": "<2.12 || >=4" }, "require-dev": { - "doctrine/coding-standard": "^12", + "doctrine/coding-standard": "^13", "doctrine/orm": "^2.13 || ^3", - "doctrine/persistence": "^2 || ^3", + "doctrine/persistence": "^2 || ^3 || ^4", "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", - "phpstan/phpstan-strict-rules": "^1.4", - "phpstan/phpstan-symfony": "^1.3", - "phpunit/phpunit": "^10.3", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-phpunit": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpstan/phpstan-symfony": "^2", + "phpunit/phpunit": "^10.3 || ^11.0 || ^12.0", "symfony/cache": "^5.4 || ^6.0 || ^7.0", "symfony/process": "^5.4 || ^6.0 || ^7.0", "symfony/yaml": "^5.4 || ^6.0 || ^7.0" @@ -2607,7 +3763,7 @@ ], "support": { "issues": "https://github.com/doctrine/migrations/issues", - "source": "https://github.com/doctrine/migrations/tree/3.8.2" + "source": "https://github.com/doctrine/migrations/tree/3.9.4" }, "funding": [ { @@ -2623,20 +3779,20 @@ "type": "tidelift" } ], - "time": "2024-10-10T21:35:27+00:00" + "time": "2025-08-19T06:41:07+00:00" }, { "name": "doctrine/orm", - "version": "3.3.1", + "version": "3.5.2", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "b1f8253105aa5382c495e5f9f8ef34e297775428" + "reference": "5a541b8b3a327ab1ea5f93b1615b4ff67a34e109" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/b1f8253105aa5382c495e5f9f8ef34e297775428", - "reference": "b1f8253105aa5382c495e5f9f8ef34e297775428", + "url": "https://api.github.com/repos/doctrine/orm/zipball/5a541b8b3a327ab1ea5f93b1615b4ff67a34e109", + "reference": "5a541b8b3a327ab1ea5f93b1615b4ff67a34e109", "shasum": "" }, "require": { @@ -2656,7 +3812,7 @@ "symfony/var-exporter": "^6.3.9 || ^7.0" }, "require-dev": { - "doctrine/coding-standard": "^12.0", + "doctrine/coding-standard": "^13.0", "phpbench/phpbench": "^1.0", "phpdocumentor/guides-cli": "^1.4", "phpstan/extension-installer": "^1.4", @@ -2664,7 +3820,7 @@ "phpstan/phpstan-deprecation-rules": "^2", "phpunit/phpunit": "^10.4.0", "psr/log": "^1 || ^2 || ^3", - "squizlabs/php_codesniffer": "3.7.2", + "squizlabs/php_codesniffer": "3.12.0", "symfony/cache": "^5.4 || ^6.2 || ^7.0" }, "suggest": { @@ -2711,40 +3867,37 @@ ], "support": { "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/3.3.1" + "source": "https://github.com/doctrine/orm/tree/3.5.2" }, - "time": "2024-12-19T07:08:14+00:00" + "time": "2025-08-08T17:00:40+00:00" }, { "name": "doctrine/persistence", - "version": "3.4.0", + "version": "4.1.1", "source": { "type": "git", "url": "https://github.com/doctrine/persistence.git", - "reference": "0ea965320cec355dba75031c1b23d4c78362e3ff" + "reference": "b9c49ad3558bb77ef973f4e173f2e9c2eca9be09" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/persistence/zipball/0ea965320cec355dba75031c1b23d4c78362e3ff", - "reference": "0ea965320cec355dba75031c1b23d4c78362e3ff", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/b9c49ad3558bb77ef973f4e173f2e9c2eca9be09", + "reference": "b9c49ad3558bb77ef973f4e173f2e9c2eca9be09", "shasum": "" }, "require": { "doctrine/event-manager": "^1 || ^2", - "php": "^7.2 || ^8.0", + "php": "^8.1", "psr/cache": "^1.0 || ^2.0 || ^3.0" }, - "conflict": { - "doctrine/common": "<2.10" - }, "require-dev": { - "doctrine/coding-standard": "^12", - "doctrine/common": "^3.0", - "phpstan/phpstan": "1.12.7", - "phpstan/phpstan-phpunit": "^1", - "phpstan/phpstan-strict-rules": "^1.1", - "phpunit/phpunit": "^8.5.38 || ^9.5", - "symfony/cache": "^4.4 || ^5.4 || ^6.0 || ^7.0" + "doctrine/coding-standard": "^14", + "phpstan/phpstan": "2.1.30", + "phpstan/phpstan-phpunit": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.58 || ^12", + "symfony/cache": "^4.4 || ^5.4 || ^6.0 || ^7.0", + "symfony/finder": "^4.4 || ^5.4 || ^6.0 || ^7.0" }, "type": "library", "autoload": { @@ -2793,7 +3946,7 @@ ], "support": { "issues": "https://github.com/doctrine/persistence/issues", - "source": "https://github.com/doctrine/persistence/tree/3.4.0" + "source": "https://github.com/doctrine/persistence/tree/4.1.1" }, "funding": [ { @@ -2809,20 +3962,20 @@ "type": "tidelift" } ], - "time": "2024-10-30T19:48:12+00:00" + "time": "2025-10-16T20:13:18+00:00" }, { "name": "doctrine/sql-formatter", - "version": "1.5.1", + "version": "1.5.2", "source": { "type": "git", "url": "https://github.com/doctrine/sql-formatter.git", - "reference": "b784cbde727cf806721451dde40eff4fec3bbe86" + "reference": "d6d00aba6fd2957fe5216fe2b7673e9985db20c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/sql-formatter/zipball/b784cbde727cf806721451dde40eff4fec3bbe86", - "reference": "b784cbde727cf806721451dde40eff4fec3bbe86", + "url": "https://api.github.com/repos/doctrine/sql-formatter/zipball/d6d00aba6fd2957fe5216fe2b7673e9985db20c8", + "reference": "d6d00aba6fd2957fe5216fe2b7673e9985db20c8", "shasum": "" }, "require": { @@ -2832,8 +3985,7 @@ "doctrine/coding-standard": "^12", "ergebnis/phpunit-slow-test-detector": "^2.14", "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^10.5", - "vimeo/psalm": "^5.24" + "phpunit/phpunit": "^10.5" }, "bin": [ "bin/sql-formatter" @@ -2863,22 +4015,22 @@ ], "support": { "issues": "https://github.com/doctrine/sql-formatter/issues", - "source": "https://github.com/doctrine/sql-formatter/tree/1.5.1" + "source": "https://github.com/doctrine/sql-formatter/tree/1.5.2" }, - "time": "2024-10-21T18:21:57+00:00" + "time": "2025-01-24T11:45:48+00:00" }, { "name": "dompdf/dompdf", - "version": "v3.0.2", + "version": "v3.1.3", "source": { "type": "git", "url": "https://github.com/dompdf/dompdf.git", - "reference": "baf4084b27c7f4b5b7a221b19a94d11327664eb8" + "reference": "baed300e4fb8226359c04395518059a136e2a2e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/dompdf/zipball/baf4084b27c7f4b5b7a221b19a94d11327664eb8", - "reference": "baf4084b27c7f4b5b7a221b19a94d11327664eb8", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/baed300e4fb8226359c04395518059a136e2a2e2", + "reference": "baed300e4fb8226359c04395518059a136e2a2e2", "shasum": "" }, "require": { @@ -2927,9 +4079,9 @@ "homepage": "https://github.com/dompdf/dompdf", "support": { "issues": "https://github.com/dompdf/dompdf/issues", - "source": "https://github.com/dompdf/dompdf/tree/v3.0.2" + "source": "https://github.com/dompdf/dompdf/tree/v3.1.3" }, - "time": "2024-12-27T20:27:37+00:00" + "time": "2025-10-14T13:10:17+00:00" }, { "name": "dompdf/php-font-lib", @@ -3024,226 +4176,39 @@ }, { "name": "egulias/email-validator", - "version": "4.0.3", - "source": { - "type": "git", - "url": "https://github.com/egulias/EmailValidator.git", - "reference": "b115554301161fa21467629f1e1391c1936de517" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/b115554301161fa21467629f1e1391c1936de517", - "reference": "b115554301161fa21467629f1e1391c1936de517", - "shasum": "" - }, - "require": { - "doctrine/lexer": "^2.0 || ^3.0", - "php": ">=8.1", - "symfony/polyfill-intl-idn": "^1.26" - }, - "require-dev": { - "phpunit/phpunit": "^10.2", - "vimeo/psalm": "^5.12" - }, - "suggest": { - "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Egulias\\EmailValidator\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Eduardo Gulias Davis" - } - ], - "description": "A library for validating emails against several RFCs", - "homepage": "https://github.com/egulias/EmailValidator", - "keywords": [ - "email", - "emailvalidation", - "emailvalidator", - "validation", - "validator" - ], - "support": { - "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/4.0.3" - }, - "funding": [ - { - "url": "https://github.com/egulias", - "type": "github" - } - ], - "time": "2024-12-27T00:36:43+00:00" - }, - { - "name": "erusev/parsedown", - "version": "1.7.4", - "source": { - "type": "git", - "url": "https://github.com/erusev/parsedown.git", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35" - }, - "type": "library", - "autoload": { - "psr-0": { - "Parsedown": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Emanuil Rusev", - "email": "hello@erusev.com", - "homepage": "http://erusev.com/" - } - ], - "description": "Parser for Markdown.", - "homepage": "http://parsedown.org/", - "keywords": [ - "markdown", - "parser" - ], - "support": { - "issues": "https://github.com/erusev/parsedown/issues", - "source": "https://github.com/erusev/parsedown/tree/1.7.x" - }, - "time": "2019-12-30T22:54:17+00:00" - }, - { - "name": "florianv/exchanger", - "version": "2.8.1", - "source": { - "type": "git", - "url": "https://github.com/florianv/exchanger.git", - "reference": "9214f51665fb907e7aa2397e21a90c456eb0c448" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/florianv/exchanger/zipball/9214f51665fb907e7aa2397e21a90c456eb0c448", - "reference": "9214f51665fb907e7aa2397e21a90c456eb0c448", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-libxml": "*", - "ext-simplexml": "*", - "php": "^7.1.3 || ^8.0", - "php-http/client-implementation": "^1.0", - "php-http/discovery": "^1.6", - "php-http/httplug": "^1.0 || ^2.0", - "psr/http-factory": "^1.0.2", - "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" - }, - "require-dev": { - "nyholm/psr7": "^1.0", - "php-http/message": "^1.7", - "php-http/mock-client": "^1.0", - "phpunit/phpunit": "^7 || ^8 || ^9.4" - }, - "suggest": { - "php-http/guzzle6-adapter": "Required to use Guzzle for sending HTTP requests", - "php-http/message": "Required to use Guzzle for sending HTTP requests" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "Exchanger\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Florian Voutzinos", - "email": "florian@voutzinos.com", - "homepage": "https://voutzinos.com/" - } - ], - "description": "Currency exchange rates framework for PHP", - "homepage": "https://github.com/florianv/exchanger", - "keywords": [ - "Rate", - "conversion", - "currency", - "exchange rates", - "money" - ], - "support": { - "issues": "https://github.com/florianv/exchanger/issues", - "source": "https://github.com/florianv/exchanger/tree/2.8.1" - }, - "time": "2023-11-03T17:11:52+00:00" - }, - { - "name": "florianv/swap", - "version": "4.3.0", + "version": "4.0.4", "source": { "type": "git", - "url": "https://github.com/florianv/swap.git", - "reference": "88edd27fcb95bdc58bbbf9e4b00539a2843d97fd" + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/florianv/swap/zipball/88edd27fcb95bdc58bbbf9e4b00539a2843d97fd", - "reference": "88edd27fcb95bdc58bbbf9e4b00539a2843d97fd", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", "shasum": "" }, "require": { - "florianv/exchanger": "^2.0", - "php": "^7.1.3 || ^8.0" + "doctrine/lexer": "^2.0 || ^3.0", + "php": ">=8.1", + "symfony/polyfill-intl-idn": "^1.26" }, "require-dev": { - "nyholm/psr7": "^1.0", - "php-http/message": "^1.7", - "php-http/mock-client": "^1.0", - "phpunit/phpunit": "^7 || ^8 || ^9" + "phpunit/phpunit": "^10.2", + "vimeo/psalm": "^5.12" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.0.x-dev" } }, "autoload": { "psr-4": { - "Swap\\": "src/" + "Egulias\\EmailValidator\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -3252,64 +4217,68 @@ ], "authors": [ { - "name": "Florian Voutzinos", - "email": "florian@voutzinos.com", - "homepage": "https://voutzinos.com/" + "name": "Eduardo Gulias Davis" } ], - "description": "Exchange rates library for PHP", + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", "keywords": [ - "Rate", - "conversion", - "currency", - "exchange rates", - "money" + "email", + "emailvalidation", + "emailvalidator", + "validation", + "validator" ], "support": { - "issues": "https://github.com/florianv/swap/issues", - "source": "https://github.com/florianv/swap/tree/4.3.0" + "issues": "https://github.com/egulias/EmailValidator/issues", + "source": "https://github.com/egulias/EmailValidator/tree/4.0.4" }, - "time": "2020-12-28T10:14:12+00:00" + "funding": [ + { + "url": "https://github.com/egulias", + "type": "github" + } + ], + "time": "2025-03-06T22:45:56+00:00" }, { - "name": "florianv/swap-bundle", - "version": "dev-master", + "name": "ergebnis/classy", + "version": "1.9.0", "source": { "type": "git", - "url": "https://github.com/florianv/symfony-swap.git", - "reference": "c8cd268ad6e2f636f10b91df9850e3941d7f5807" + "url": "https://github.com/ergebnis/classy.git", + "reference": "05c3ac7d8d9d337c4cf1d5602a339f57cb2a27ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/florianv/symfony-swap/zipball/c8cd268ad6e2f636f10b91df9850e3941d7f5807", - "reference": "c8cd268ad6e2f636f10b91df9850e3941d7f5807", + "url": "https://api.github.com/repos/ergebnis/classy/zipball/05c3ac7d8d9d337c4cf1d5602a339f57cb2a27ef", + "reference": "05c3ac7d8d9d337c4cf1d5602a339f57cb2a27ef", "shasum": "" }, "require": { - "florianv/swap": "^4.0", - "php": "^7.1.3|^8.0", - "symfony/framework-bundle": "~3.0|~4.0|~5.0|~6.0|~7.0" + "ext-tokenizer": "*", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" }, "require-dev": { - "nyholm/psr7": "^1.1", - "php-http/guzzle6-adapter": "^1.0", - "php-http/message": "^1.7", - "phpunit/phpunit": "~5.7|~6.0|~7.0|~8.0|~9.0", - "symfony/cache": "~3.0|~4.0|~5.0|~6.0|~7.0" - }, - "suggest": { - "symfony/cache": "For caching" - }, - "default-branch": true, - "type": "symfony-bundle", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } + "ergebnis/composer-normalize": "^2.48.1", + "ergebnis/license": "^2.7.0", + "ergebnis/php-cs-fixer-config": "^6.54.0", + "ergebnis/phpstan-rules": "^2.11.0", + "ergebnis/phpunit-slow-test-detector": "^2.20.0", + "fakerphp/faker": "^1.24.1", + "infection/infection": "~0.26.6", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.22", + "phpstan/phpstan-deprecation-rules": "^2.0.3", + "phpstan/phpstan-phpunit": "^2.0.7", + "phpstan/phpstan-strict-rules": "^2.0.6", + "phpunit/phpunit": "^9.6.19", + "rector/rector": "^2.1.4" }, + "type": "library", "autoload": { "psr-4": { - "Florianv\\SwapBundle\\": "" + "Ergebnis\\Classy\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3318,50 +4287,50 @@ ], "authors": [ { - "name": "Florian Voutzinos", - "email": "florian@voutzinos.com", - "homepage": "http://florian.voutzinos.com/" + "name": "Andreas Mรถller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com/" } ], - "description": "Integrates the Swap library with Symfony", - "homepage": "https://github.com/florianv/FlorianvSwapBundle", + "description": "Provides a finder for classy constructs (classes, enums, interfaces, and traits).", + "homepage": "https://github.com/ergebnis/classy", "keywords": [ - "Rate", - "bundle", - "conversion", - "currency", - "exchange", - "money", - "symfony" + "classes", + "classy", + "constructs", + "finder", + "interfaces", + "traits" ], "support": { - "issues": "https://github.com/florianv/symfony-swap/issues", - "source": "https://github.com/florianv/symfony-swap/tree/master" + "issues": "https://github.com/ergebnis/classy/issues", + "source": "https://github.com/ergebnis/classy" }, - "time": "2024-07-09T13:51:01+00:00" + "time": "2025-09-04T10:17:22+00:00" }, { "name": "gregwar/captcha", - "version": "v1.2.1", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/Gregwar/Captcha.git", - "reference": "229d3cdfe33d6f1349e0aec94a26e9205a6db08e" + "reference": "4edbcd09fde4353b94ce550f43460eba73baf2cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Gregwar/Captcha/zipball/229d3cdfe33d6f1349e0aec94a26e9205a6db08e", - "reference": "229d3cdfe33d6f1349e0aec94a26e9205a6db08e", + "url": "https://api.github.com/repos/Gregwar/Captcha/zipball/4edbcd09fde4353b94ce550f43460eba73baf2cc", + "reference": "4edbcd09fde4353b94ce550f43460eba73baf2cc", "shasum": "" }, "require": { + "ext-fileinfo": "*", "ext-gd": "*", "ext-mbstring": "*", "php": ">=5.3.0", "symfony/finder": "*" }, "require-dev": { - "phpunit/phpunit": "^6.4" + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6.4 || ^7.0 || ^8.0 || ^9.0" }, "type": "library", "autoload": { @@ -3393,22 +4362,22 @@ ], "support": { "issues": "https://github.com/Gregwar/Captcha/issues", - "source": "https://github.com/Gregwar/Captcha/tree/v1.2.1" + "source": "https://github.com/Gregwar/Captcha/tree/v1.3.0" }, - "time": "2023-09-26T13:45:37+00:00" + "time": "2025-06-23T12:25:54+00:00" }, { "name": "gregwar/captcha-bundle", - "version": "v2.3.0", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/Gregwar/CaptchaBundle.git", - "reference": "8eb95c0911a1db9e3b2f368f6319e0945b959a6c" + "reference": "090a3754f02cadb7ecdb531b090322dbe5c03c75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Gregwar/CaptchaBundle/zipball/8eb95c0911a1db9e3b2f368f6319e0945b959a6c", - "reference": "8eb95c0911a1db9e3b2f368f6319e0945b959a6c", + "url": "https://api.github.com/repos/Gregwar/CaptchaBundle/zipball/090a3754f02cadb7ecdb531b090322dbe5c03c75", + "reference": "090a3754f02cadb7ecdb531b090322dbe5c03c75", "shasum": "" }, "require": { @@ -3428,7 +4397,7 @@ "type": "symfony-bundle", "autoload": { "psr-4": { - "Gregwar\\CaptchaBundle\\": "/" + "Gregwar\\CaptchaBundle\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -3460,28 +4429,28 @@ ], "support": { "issues": "https://github.com/Gregwar/CaptchaBundle/issues", - "source": "https://github.com/Gregwar/CaptchaBundle/tree/v2.3.0" + "source": "https://github.com/Gregwar/CaptchaBundle/tree/v2.4.0" }, - "time": "2024-06-06T13:14:57+00:00" + "time": "2025-06-24T10:25:23+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "7.9.2", + "version": "7.10.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "d281ed313b989f213357e3be1a179f02196ac99b" + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", - "reference": "d281ed313b989f213357e3be1a179f02196ac99b", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0.3", - "guzzlehttp/psr7": "^2.7.0", + "guzzlehttp/promises": "^2.3", + "guzzlehttp/psr7": "^2.8", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -3572,7 +4541,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.9.2" + "source": "https://github.com/guzzle/guzzle/tree/7.10.0" }, "funding": [ { @@ -3588,20 +4557,20 @@ "type": "tidelift" } ], - "time": "2024-07-24T11:22:20+00:00" + "time": "2025-08-23T22:36:01+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.0.4", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455" + "reference": "481557b130ef3790cf82b713667b43030dc9c957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455", - "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", "shasum": "" }, "require": { @@ -3609,7 +4578,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.39 || ^9.6.20" + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "type": "library", "extra": { @@ -3655,7 +4624,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.4" + "source": "https://github.com/guzzle/promises/tree/2.3.0" }, "funding": [ { @@ -3671,20 +4640,20 @@ "type": "tidelift" } ], - "time": "2024-10-17T10:06:22+00:00" + "time": "2025-08-22T14:34:08+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.7.0", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" + "reference": "21dc724a0583619cd1652f673303492272778051" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201", - "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", + "reference": "21dc724a0583619cd1652f673303492272778051", "shasum": "" }, "require": { @@ -3700,7 +4669,7 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "0.9.0", - "phpunit/phpunit": "^8.5.39 || ^9.6.20" + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -3771,7 +4740,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.7.0" + "source": "https://github.com/guzzle/psr7/tree/2.8.0" }, "funding": [ { @@ -3787,20 +4756,20 @@ "type": "tidelift" } ], - "time": "2024-07-18T11:15:46+00:00" + "time": "2025-08-23T21:21:41+00:00" }, { "name": "hshn/base64-encoded-file", - "version": "v5.0.1", + "version": "v5.0.3", "source": { "type": "git", "url": "https://github.com/hshn/base64-encoded-file.git", - "reference": "54fa81461ba4fbf5b67ed71d22b43ea5cc8c8748" + "reference": "74984c7e69fbed9378dbf1d64e632522cc1b6d95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hshn/base64-encoded-file/zipball/54fa81461ba4fbf5b67ed71d22b43ea5cc8c8748", - "reference": "54fa81461ba4fbf5b67ed71d22b43ea5cc8c8748", + "url": "https://api.github.com/repos/hshn/base64-encoded-file/zipball/74984c7e69fbed9378dbf1d64e632522cc1b6d95", + "reference": "74984c7e69fbed9378dbf1d64e632522cc1b6d95", "shasum": "" }, "require": { @@ -3847,9 +4816,9 @@ "description": "Provides handling base64 encoded files, and the integration of symfony/form", "support": { "issues": "https://github.com/hshn/base64-encoded-file/issues", - "source": "https://github.com/hshn/base64-encoded-file/tree/v5.0.1" + "source": "https://github.com/hshn/base64-encoded-file/tree/v5.0.3" }, - "time": "2023-12-24T07:23:07+00:00" + "time": "2025-10-06T10:34:52+00:00" }, { "name": "imagine/imagine", @@ -3915,28 +4884,28 @@ }, { "name": "jbtronics/2fa-webauthn", - "version": "v2.2.2", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/jbtronics/2fa-webauthn.git", - "reference": "4341335ab9667f7347a24d60c29d143f25371e82" + "reference": "542424bcc51f06932cbecfd7b75afbb5e107c9ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jbtronics/2fa-webauthn/zipball/4341335ab9667f7347a24d60c29d143f25371e82", - "reference": "4341335ab9667f7347a24d60c29d143f25371e82", + "url": "https://api.github.com/repos/jbtronics/2fa-webauthn/zipball/542424bcc51f06932cbecfd7b75afbb5e107c9ce", + "reference": "542424bcc51f06932cbecfd7b75afbb5e107c9ce", "shasum": "" }, "require": { "ext-json": "*", "nyholm/psr7": "^1.5", - "php": "^8.1", + "php": "^8.2", "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|^6.1|^7.0", "symfony/uid": "^6.0|^7.0", - "web-auth/webauthn-lib": "^4.7" + "web-auth/webauthn-lib": "^5.2" }, "require-dev": { "phpunit/phpunit": "^9.5", @@ -3958,7 +4927,7 @@ "email": "mail@jan-boehmer.de" } ], - "description": "Webauthn Two-Factor-Authentictication Plugin for scheb/2fa", + "description": "Webauthn Two-Factor-Authentication Plugin for scheb/2fa", "keywords": [ "2fa", "scheb-2fa", @@ -3969,22 +4938,22 @@ ], "support": { "issues": "https://github.com/jbtronics/2fa-webauthn/issues", - "source": "https://github.com/jbtronics/2fa-webauthn/tree/v2.2.2" + "source": "https://github.com/jbtronics/2fa-webauthn/tree/v3.0.0" }, - "time": "2024-07-21T19:24:26+00:00" + "time": "2025-08-14T15:12:48+00:00" }, { "name": "jbtronics/dompdf-font-loader-bundle", - "version": "v1.1.2", + "version": "v1.1.5", "source": { "type": "git", "url": "https://github.com/jbtronics/dompdf-font-loader-bundle.git", - "reference": "de3a2982e626144bc543119585ed46dc4c884bea" + "reference": "83a0e50ecceefea0a63915dae758e00788fd067e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jbtronics/dompdf-font-loader-bundle/zipball/de3a2982e626144bc543119585ed46dc4c884bea", - "reference": "de3a2982e626144bc543119585ed46dc4c884bea", + "url": "https://api.github.com/repos/jbtronics/dompdf-font-loader-bundle/zipball/83a0e50ecceefea0a63915dae758e00788fd067e", + "reference": "83a0e50ecceefea0a63915dae758e00788fd067e", "shasum": "" }, "require": { @@ -4024,9 +4993,95 @@ ], "support": { "issues": "https://github.com/jbtronics/dompdf-font-loader-bundle/issues", - "source": "https://github.com/jbtronics/dompdf-font-loader-bundle/tree/v1.1.2" + "source": "https://github.com/jbtronics/dompdf-font-loader-bundle/tree/v1.1.5" + }, + "time": "2025-07-25T20:29:05+00:00" + }, + { + "name": "jbtronics/settings-bundle", + "version": "v3.1.1", + "source": { + "type": "git", + "url": "https://github.com/jbtronics/settings-bundle.git", + "reference": "1067dd3d816cd0a6be7ac3d3989587ea05040bd4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jbtronics/settings-bundle/zipball/1067dd3d816cd0a6be7ac3d3989587ea05040bd4", + "reference": "1067dd3d816cd0a6be7ac3d3989587ea05040bd4", + "shasum": "" + }, + "require": { + "ergebnis/classy": "^1.6", + "ext-json": "*", + "php": "^8.1", + "symfony/deprecation-contracts": "^3.4", + "symfony/form": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/translation": "^7.0|^6.4", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0" + }, + "require-dev": { + "doctrine/doctrine-bundle": "^2.11", + "doctrine/doctrine-fixtures-bundle": "^3.5", + "doctrine/orm": "^3.0", + "ekino/phpstan-banned-code": "^1.0", + "phpstan/extension-installer": "^1.3", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-strict-rules": "^1.5", + "phpstan/phpstan-symfony": "^1.3", + "phpunit/phpunit": "^9.5", + "roave/security-advisories": "dev-latest", + "symfony/console": "^6.4|^7.0", + "symfony/phpunit-bridge": "^6.4|^7.0", + "symfony/security-csrf": "^7.0|^6.4", + "symfony/twig-bridge": "^6.4|^7.0" + }, + "suggest": { + "doctrine/doctrine-bundle": "To use the doctrine ORM storage", + "symfony/twig-bridge": "Allows to access settings in twig templates" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Jbtronics\\SettingsBundle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Bรถhmer", + "email": "mail@jan-boehmer.de" + } + ], + "description": "A symfony bundle to easily create typesafe, user-configurable settings for symfony applications", + "keywords": [ + "Settings", + "config", + "symfony", + "symfony-bundle", + "user-configurable" + ], + "support": { + "issues": "https://github.com/jbtronics/settings-bundle/issues", + "source": "https://github.com/jbtronics/settings-bundle/tree/v3.1.1" }, - "time": "2024-06-06T17:42:51+00:00" + "funding": [ + { + "url": "https://www.paypal.me/do9jhb", + "type": "custom" + }, + { + "url": "https://github.com/jbtronics", + "type": "github" + } + ], + "time": "2025-09-22T22:00:15+00:00" }, { "name": "jfcherng/php-color-output", @@ -4325,16 +5380,16 @@ }, { "name": "knpuniversity/oauth2-client-bundle", - "version": "v2.18.3", + "version": "v2.19.0", "source": { "type": "git", "url": "https://github.com/knpuniversity/oauth2-client-bundle.git", - "reference": "c38ca88a70aae3694ca346a41b13b9a8f6e33ed4" + "reference": "cd1cb6945a46df81be6e94944872546ca4bf335c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/knpuniversity/oauth2-client-bundle/zipball/c38ca88a70aae3694ca346a41b13b9a8f6e33ed4", - "reference": "c38ca88a70aae3694ca346a41b13b9a8f6e33ed4", + "url": "https://api.github.com/repos/knpuniversity/oauth2-client-bundle/zipball/cd1cb6945a46df81be6e94944872546ca4bf335c", + "reference": "cd1cb6945a46df81be6e94944872546ca4bf335c", "shasum": "" }, "require": { @@ -4376,115 +5431,291 @@ "oauth", "oauth2" ], - "support": { - "issues": "https://github.com/knpuniversity/oauth2-client-bundle/issues", - "source": "https://github.com/knpuniversity/oauth2-client-bundle/tree/v2.18.3" - }, - "time": "2024-10-02T14:26:09+00:00" + "support": { + "issues": "https://github.com/knpuniversity/oauth2-client-bundle/issues", + "source": "https://github.com/knpuniversity/oauth2-client-bundle/tree/v2.19.0" + }, + "time": "2025-09-17T15:00:36+00:00" + }, + { + "name": "lcobucci/clock", + "version": "3.3.1", + "source": { + "type": "git", + "url": "https://github.com/lcobucci/clock.git", + "reference": "db3713a61addfffd615b79bf0bc22f0ccc61b86b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lcobucci/clock/zipball/db3713a61addfffd615b79bf0bc22f0ccc61b86b", + "reference": "db3713a61addfffd615b79bf0bc22f0ccc61b86b", + "shasum": "" + }, + "require": { + "php": "~8.2.0 || ~8.3.0 || ~8.4.0", + "psr/clock": "^1.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "infection/infection": "^0.29", + "lcobucci/coding-standard": "^11.1.0", + "phpstan/extension-installer": "^1.3.1", + "phpstan/phpstan": "^1.10.25", + "phpstan/phpstan-deprecation-rules": "^1.1.3", + "phpstan/phpstan-phpunit": "^1.3.13", + "phpstan/phpstan-strict-rules": "^1.5.1", + "phpunit/phpunit": "^11.3.6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Lcobucci\\Clock\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Luรญs Cobucci", + "email": "lcobucci@gmail.com" + } + ], + "description": "Yet another clock abstraction", + "support": { + "issues": "https://github.com/lcobucci/clock/issues", + "source": "https://github.com/lcobucci/clock/tree/3.3.1" + }, + "funding": [ + { + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" + } + ], + "time": "2024-09-24T20:45:14+00:00" + }, + { + "name": "lcobucci/jwt", + "version": "5.6.0", + "source": { + "type": "git", + "url": "https://github.com/lcobucci/jwt.git", + "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/bb3e9f21e4196e8afc41def81ef649c164bca25e", + "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-sodium": "*", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "psr/clock": "^1.0" + }, + "require-dev": { + "infection/infection": "^0.29", + "lcobucci/clock": "^3.2", + "lcobucci/coding-standard": "^11.0", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.10.7", + "phpstan/phpstan-deprecation-rules": "^1.1.3", + "phpstan/phpstan-phpunit": "^1.3.10", + "phpstan/phpstan-strict-rules": "^1.5.0", + "phpunit/phpunit": "^11.1" + }, + "suggest": { + "lcobucci/clock": ">= 3.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Lcobucci\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Luรญs Cobucci", + "email": "lcobucci@gmail.com", + "role": "Developer" + } + ], + "description": "A simple library to work with JSON Web Token and JSON Web Signature", + "keywords": [ + "JWS", + "jwt" + ], + "support": { + "issues": "https://github.com/lcobucci/jwt/issues", + "source": "https://github.com/lcobucci/jwt/tree/5.6.0" + }, + "funding": [ + { + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" + } + ], + "time": "2025-10-17T11:30:53+00:00" }, { - "name": "lcobucci/clock", - "version": "3.0.0", + "name": "league/commonmark", + "version": "2.7.1", "source": { "type": "git", - "url": "https://github.com/lcobucci/clock.git", - "reference": "039ef98c6b57b101d10bd11d8fdfda12cbd996dc" + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "10732241927d3971d28e7ea7b5712721fa2296ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/clock/zipball/039ef98c6b57b101d10bd11d8fdfda12cbd996dc", - "reference": "039ef98c6b57b101d10bd11d8fdfda12cbd996dc", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/10732241927d3971d28e7ea7b5712721fa2296ca", + "reference": "10732241927d3971d28e7ea7b5712721fa2296ca", "shasum": "" }, "require": { - "php": "~8.1.0 || ~8.2.0", - "psr/clock": "^1.0" - }, - "provide": { - "psr/clock-implementation": "1.0" + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.16" }, "require-dev": { - "infection/infection": "^0.26", - "lcobucci/coding-standard": "^9.0", - "phpstan/extension-installer": "^1.2", - "phpstan/phpstan": "^1.9.4", - "phpstan/phpstan-deprecation-rules": "^1.1.1", - "phpstan/phpstan-phpunit": "^1.3.2", - "phpstan/phpstan-strict-rules": "^1.4.4", - "phpunit/phpunit": "^9.5.27" + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.31.1", + "commonmark/commonmark.js": "0.31.1", + "composer/package-versions-deprecated": "^1.8", + "embed/embed": "^4.4", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "^1.4 || ^2.0", + "nyholm/psr7": "^1.5", + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3 | ^6.0 | ^7.0", + "symfony/process": "^5.4 | ^6.0 | ^7.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0", + "unleashedtech/php-coding-standard": "^3.1.1", + "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.8-dev" + } + }, "autoload": { "psr-4": { - "Lcobucci\\Clock\\": "src" + "League\\CommonMark\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Luรญs Cobucci", - "email": "lcobucci@gmail.com" + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com/", + "role": "Lead Developer" } ], - "description": "Yet another clock abstraction", + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com/", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], "support": { - "issues": "https://github.com/lcobucci/clock/issues", - "source": "https://github.com/lcobucci/clock/tree/3.0.0" + "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" }, "funding": [ { - "url": "https://github.com/lcobucci", + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", "type": "github" }, { - "url": "https://www.patreon.com/lcobucci", - "type": "patreon" + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" } ], - "time": "2022-12-19T15:00:24+00:00" + "time": "2025-07-20T12:47:49+00:00" }, { - "name": "lcobucci/jwt", - "version": "5.3.0", + "name": "league/config", + "version": "v1.2.0", "source": { "type": "git", - "url": "https://github.com/lcobucci/jwt.git", - "reference": "08071d8d2c7f4b00222cc4b1fb6aa46990a80f83" + "url": "https://github.com/thephpleague/config.git", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/08071d8d2c7f4b00222cc4b1fb6aa46990a80f83", - "reference": "08071d8d2c7f4b00222cc4b1fb6aa46990a80f83", + "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", "shasum": "" }, "require": { - "ext-openssl": "*", - "ext-sodium": "*", - "php": "~8.1.0 || ~8.2.0 || ~8.3.0", - "psr/clock": "^1.0" + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" }, "require-dev": { - "infection/infection": "^0.27.0", - "lcobucci/clock": "^3.0", - "lcobucci/coding-standard": "^11.0", - "phpbench/phpbench": "^1.2.9", - "phpstan/extension-installer": "^1.2", - "phpstan/phpstan": "^1.10.7", - "phpstan/phpstan-deprecation-rules": "^1.1.3", - "phpstan/phpstan-phpunit": "^1.3.10", - "phpstan/phpstan-strict-rules": "^1.5.0", - "phpunit/phpunit": "^10.2.6" - }, - "suggest": { - "lcobucci/clock": ">= 3.0" + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, "autoload": { "psr-4": { - "Lcobucci\\JWT\\": "src" + "League\\Config\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -4493,63 +5724,83 @@ ], "authors": [ { - "name": "Luรญs Cobucci", - "email": "lcobucci@gmail.com", - "role": "Developer" + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com/", + "role": "Lead Developer" } ], - "description": "A simple library to work with JSON Web Token and JSON Web Signature", + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com/", "keywords": [ - "JWS", - "jwt" + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" ], "support": { - "issues": "https://github.com/lcobucci/jwt/issues", - "source": "https://github.com/lcobucci/jwt/tree/5.3.0" + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" }, "funding": [ { - "url": "https://github.com/lcobucci", - "type": "github" + "url": "https://www.colinodell.com/sponsor", + "type": "custom" }, { - "url": "https://www.patreon.com/lcobucci", - "type": "patreon" + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" } ], - "time": "2024-04-11T23:07:54+00:00" + "time": "2022-12-11T20:36:23+00:00" }, { "name": "league/csv", - "version": "9.8.0", + "version": "9.27.0", "source": { "type": "git", "url": "https://github.com/thephpleague/csv.git", - "reference": "9d2e0265c5d90f5dd601bc65ff717e05cec19b47" + "reference": "cb491b1ba3c42ff2bcd0113814f4256b42bae845" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/csv/zipball/9d2e0265c5d90f5dd601bc65ff717e05cec19b47", - "reference": "9d2e0265c5d90f5dd601bc65ff717e05cec19b47", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/cb491b1ba3c42ff2bcd0113814f4256b42bae845", + "reference": "cb491b1ba3c42ff2bcd0113814f4256b42bae845", "shasum": "" }, "require": { - "ext-json": "*", - "ext-mbstring": "*", - "php": "^7.4 || ^8.0" + "ext-filter": "*", + "php": "^8.1.2" }, "require-dev": { - "ext-curl": "*", "ext-dom": "*", - "friendsofphp/php-cs-fixer": "^v3.4.0", - "phpstan/phpstan": "^1.3.0", - "phpstan/phpstan-phpunit": "^1.0.0", - "phpstan/phpstan-strict-rules": "^1.1.0", - "phpunit/phpunit": "^9.5.11" + "ext-xdebug": "*", + "friendsofphp/php-cs-fixer": "^3.75.0", + "phpbench/phpbench": "^1.4.1", + "phpstan/phpstan": "^1.12.27", + "phpstan/phpstan-deprecation-rules": "^1.2.1", + "phpstan/phpstan-phpunit": "^1.4.2", + "phpstan/phpstan-strict-rules": "^1.6.2", + "phpunit/phpunit": "^10.5.16 || ^11.5.22 || ^12.3.6", + "symfony/var-dumper": "^6.4.8 || ^7.3.0" }, "suggest": { - "ext-dom": "Required to use the XMLConverter and or the HTMLConverter classes", - "ext-iconv": "Needed to ease transcoding CSV using iconv stream filters" + "ext-dom": "Required to use the XMLConverter and the HTMLConverter classes", + "ext-iconv": "Needed to ease transcoding CSV using iconv stream filters", + "ext-mbstring": "Needed to ease transcoding CSV using mb stream filters", + "ext-mysqli": "Requiered to use the package with the MySQLi extension", + "ext-pdo": "Required to use the package with the PDO extension", + "ext-pgsql": "Requiered to use the package with the PgSQL extension", + "ext-sqlite3": "Required to use the package with the SQLite3 extension" }, "type": "library", "extra": { @@ -4601,7 +5852,7 @@ "type": "github" } ], - "time": "2022-01-04T00:13:07+00:00" + "time": "2025-10-16T08:22:09+00:00" }, { "name": "league/html-to-markdown", @@ -4694,16 +5945,16 @@ }, { "name": "league/oauth2-client", - "version": "2.8.0", + "version": "2.8.1", "source": { "type": "git", "url": "https://github.com/thephpleague/oauth2-client.git", - "reference": "3d5cf8d0543731dfb725ab30e4d7289891991e13" + "reference": "9df2924ca644736c835fc60466a3a60390d334f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/3d5cf8d0543731dfb725ab30e4d7289891991e13", - "reference": "3d5cf8d0543731dfb725ab30e4d7289891991e13", + "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/9df2924ca644736c835fc60466a3a60390d334f9", + "reference": "9df2924ca644736c835fc60466a3a60390d334f9", "shasum": "" }, "require": { @@ -4753,9 +6004,9 @@ ], "support": { "issues": "https://github.com/thephpleague/oauth2-client/issues", - "source": "https://github.com/thephpleague/oauth2-client/tree/2.8.0" + "source": "https://github.com/thephpleague/oauth2-client/tree/2.8.1" }, - "time": "2024-12-11T05:05:52+00:00" + "time": "2025-02-26T04:37:30+00:00" }, { "name": "league/uri", @@ -5015,22 +6266,23 @@ }, { "name": "liip/imagine-bundle", - "version": "2.13.3", + "version": "2.15.0", "source": { "type": "git", "url": "https://github.com/liip/LiipImagineBundle.git", - "reference": "3faccde327f91368e51d05ecad49a9cd915abd81" + "reference": "f8c98a5a962806f26571db219412b64266c763d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/liip/LiipImagineBundle/zipball/3faccde327f91368e51d05ecad49a9cd915abd81", - "reference": "3faccde327f91368e51d05ecad49a9cd915abd81", + "url": "https://api.github.com/repos/liip/LiipImagineBundle/zipball/f8c98a5a962806f26571db219412b64266c763d8", + "reference": "f8c98a5a962806f26571db219412b64266c763d8", "shasum": "" }, "require": { "ext-mbstring": "*", "imagine/imagine": "^1.3.2", "php": "^7.2|^8.0", + "symfony/deprecation-contracts": "^2.5 || ^3", "symfony/filesystem": "^3.4|^4.4|^5.3|^6.0|^7.0", "symfony/finder": "^3.4|^4.4|^5.3|^6.0|^7.0", "symfony/framework-bundle": "^3.4.23|^4.4|^5.3|^6.0|^7.0", @@ -5062,33 +6314,219 @@ "symfony/validator": "^3.4|^4.4|^5.3|^6.0|^7.0", "symfony/yaml": "^3.4|^4.4|^5.3|^6.0|^7.0" }, - "suggest": { - "alcaeus/mongo-php-adapter": "required for mongodb components", - "amazonwebservices/aws-sdk-for-php": "required to use AWS version 1 cache resolver", - "aws/aws-sdk-php": "required to use AWS version 2/3 cache resolver", - "doctrine/mongodb-odm": "required to use mongodb-backed doctrine components", - "enqueue/enqueue-bundle": "^0.9 add if you like to process images in background", - "ext-exif": "required to read EXIF metadata from images", - "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" + "suggest": { + "alcaeus/mongo-php-adapter": "required for mongodb components", + "amazonwebservices/aws-sdk-for-php": "required to use AWS version 1 cache resolver", + "aws/aws-sdk-php": "required to use AWS version 2/3 cache resolver", + "doctrine/mongodb-odm": "required to use mongodb-backed doctrine components", + "enqueue/enqueue-bundle": "^0.9 add if you like to process images in background", + "ext-exif": "required to read EXIF metadata from images", + "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" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Liip\\ImagineBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Liip and other contributors", + "homepage": "https://github.com/liip/LiipImagineBundle/contributors" + } + ], + "description": "This bundle provides an image manipulation abstraction toolkit for Symfony-based projects.", + "homepage": "https://www.liip.ch/", + "keywords": [ + "bundle", + "image", + "imagine", + "liip", + "manipulation", + "photos", + "pictures", + "symfony", + "transformation" + ], + "support": { + "issues": "https://github.com/liip/LiipImagineBundle/issues", + "source": "https://github.com/liip/LiipImagineBundle/tree/2.15.0" + }, + "time": "2025-10-09T06:49:28+00:00" + }, + { + "name": "lorenzo/pinky", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/lorenzo/pinky.git", + "reference": "e1b1bdb2c132b8a7ba32bca64d2443f646ddbd17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lorenzo/pinky/zipball/e1b1bdb2c132b8a7ba32bca64d2443f646ddbd17", + "reference": "e1b1bdb2c132b8a7ba32bca64d2443f646ddbd17", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xsl": "*", + "php": ">=5.6.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.21 || ^9.5.10" + }, + "type": "library", + "autoload": { + "files": [ + "src/pinky.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jose Lorenzo Rodriguez", + "email": "jose.zap@gmail.com" + } + ], + "description": "A Foundation for Emails (Inky) template transpiler", + "keywords": [ + "email", + "foundation", + "inky", + "template", + "zurb" + ], + "support": { + "issues": "https://github.com/lorenzo/pinky/issues", + "source": "https://github.com/lorenzo/pinky/tree/1.1.0" + }, + "time": "2023-07-31T13:36:50+00:00" + }, + { + "name": "maennchen/zipstream-php", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/c4c5803cc1f93df3d2448478ef79394a5981cc58", + "reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58", + "shasum": "" + }, + "require": { + "myclabs/php-enum": "^1.5", + "php": ">= 7.1", + "psr/http-message": "^1.0", + "symfony/polyfill-mbstring": "^1.0" + }, + "require-dev": { + "ext-zip": "*", + "guzzlehttp/guzzle": ">= 6.3", + "mikey179/vfsstream": "^1.6", + "phpunit/phpunit": ">= 7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Mรคnnchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "Andrรกs Kolesรกr", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/2.1.0" + }, + "funding": [ + { + "url": "https://github.com/maennchen", + "type": "github" + }, + { + "url": "https://opencollective.com/zipstream", + "type": "open_collective" + } + ], + "time": "2020-05-30T13:11:16+00:00" + }, + { + "name": "markbaker/complex", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPComplex.git", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9" }, - "type": "symfony-bundle", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", "autoload": { "psr-4": { - "Liip\\ImagineBundle\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Complex\\": "classes/src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5096,57 +6534,54 @@ ], "authors": [ { - "name": "Liip and other contributors", - "homepage": "https://github.com/liip/LiipImagineBundle/contributors" + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" } ], - "description": "This bundle provides an image manipulation abstraction toolkit for Symfony-based projects.", - "homepage": "https://www.liip.ch/", + "description": "PHP Class for working with complex numbers", + "homepage": "https://github.com/MarkBaker/PHPComplex", "keywords": [ - "bundle", - "image", - "imagine", - "liip", - "manipulation", - "photos", - "pictures", - "symfony", - "transformation" + "complex", + "mathematics" ], "support": { - "issues": "https://github.com/liip/LiipImagineBundle/issues", - "source": "https://github.com/liip/LiipImagineBundle/tree/2.13.3" + "issues": "https://github.com/MarkBaker/PHPComplex/issues", + "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2" }, - "time": "2024-12-12T09:38:23+00:00" + "time": "2022-12-06T16:21:08+00:00" }, { - "name": "lorenzo/pinky", - "version": "1.1.0", + "name": "markbaker/matrix", + "version": "3.0.1", "source": { "type": "git", - "url": "https://github.com/lorenzo/pinky.git", - "reference": "e1b1bdb2c132b8a7ba32bca64d2443f646ddbd17" + "url": "https://github.com/MarkBaker/PHPMatrix.git", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lorenzo/pinky/zipball/e1b1bdb2c132b8a7ba32bca64d2443f646ddbd17", - "reference": "e1b1bdb2c132b8a7ba32bca64d2443f646ddbd17", + "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-xsl": "*", - "php": ">=5.6.0" + "php": "^7.1 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.21 || ^9.5.10" + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "^4.0", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "sebastian/phpcpd": "^4.0", + "squizlabs/php_codesniffer": "^3.7" }, "type": "library", "autoload": { - "files": [ - "src/pinky.php" - ] + "psr-4": { + "Matrix\\": "classes/src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5154,36 +6589,35 @@ ], "authors": [ { - "name": "Jose Lorenzo Rodriguez", - "email": "jose.zap@gmail.com" + "name": "Mark Baker", + "email": "mark@demon-angel.eu" } ], - "description": "A Foundation for Emails (Inky) template transpiler", + "description": "PHP Class for working with matrices", + "homepage": "https://github.com/MarkBaker/PHPMatrix", "keywords": [ - "email", - "foundation", - "inky", - "template", - "zurb" + "mathematics", + "matrix", + "vector" ], "support": { - "issues": "https://github.com/lorenzo/pinky/issues", - "source": "https://github.com/lorenzo/pinky/tree/1.1.0" + "issues": "https://github.com/MarkBaker/PHPMatrix/issues", + "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1" }, - "time": "2023-07-31T13:36:50+00:00" + "time": "2022-12-02T22:17:43+00:00" }, { "name": "masterminds/html5", - "version": "2.9.0", + "version": "2.10.0", "source": { "type": "git", "url": "https://github.com/Masterminds/html5-php.git", - "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6" + "reference": "fcf91eb64359852f00d921887b219479b4f21251" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", - "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251", + "reference": "fcf91eb64359852f00d921887b219479b4f21251", "shasum": "" }, "require": { @@ -5235,22 +6669,22 @@ ], "support": { "issues": "https://github.com/Masterminds/html5-php/issues", - "source": "https://github.com/Masterminds/html5-php/tree/2.9.0" + "source": "https://github.com/Masterminds/html5-php/tree/2.10.0" }, - "time": "2024-03-31T07:05:07+00:00" + "time": "2025-07-25T09:04:22+00:00" }, { "name": "monolog/monolog", - "version": "3.8.1", + "version": "3.9.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4" + "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/aef6ee73a77a66e404dd6540934a9ef1b3c855b4", - "reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6", + "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6", "shasum": "" }, "require": { @@ -5328,7 +6762,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.8.1" + "source": "https://github.com/Seldaek/monolog/tree/3.9.0" }, "funding": [ { @@ -5340,41 +6774,104 @@ "type": "tidelift" } ], - "time": "2024-12-05T17:15:07+00:00" + "time": "2025-03-24T10:02:05+00:00" + }, + { + "name": "myclabs/php-enum", + "version": "1.8.5", + "source": { + "type": "git", + "url": "https://github.com/myclabs/php-enum.git", + "reference": "e7be26966b7398204a234f8673fdad5ac6277802" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/php-enum/zipball/e7be26966b7398204a234f8673fdad5ac6277802", + "reference": "e7be26966b7398204a234f8673fdad5ac6277802", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "1.*", + "vimeo/psalm": "^4.6.2 || ^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "MyCLabs\\Enum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP Enum contributors", + "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" + } + ], + "description": "PHP Enum implementation", + "homepage": "https://github.com/myclabs/php-enum", + "keywords": [ + "enum" + ], + "support": { + "issues": "https://github.com/myclabs/php-enum/issues", + "source": "https://github.com/myclabs/php-enum/tree/1.8.5" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum", + "type": "tidelift" + } + ], + "time": "2025-01-14T11:49:03+00:00" }, { "name": "nbgrp/onelogin-saml-bundle", - "version": "v1.4.0", + "version": "v2.1.0", "source": { "type": "git", "url": "https://github.com/nbgrp/onelogin-saml-bundle.git", - "reference": "3341544e72b699ab69357ab38cee9c80941ce1c6" + "reference": "087402c69ef87e0a34d9b708661deecd00fd190a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nbgrp/onelogin-saml-bundle/zipball/3341544e72b699ab69357ab38cee9c80941ce1c6", - "reference": "3341544e72b699ab69357ab38cee9c80941ce1c6", + "url": "https://api.github.com/repos/nbgrp/onelogin-saml-bundle/zipball/087402c69ef87e0a34d9b708661deecd00fd190a", + "reference": "087402c69ef87e0a34d9b708661deecd00fd190a", "shasum": "" }, "require": { "onelogin/php-saml": "^4", - "php": "^8.1", + "php": "^8.2", "psr/log": "^1 || ^2 || ^3", - "symfony/config": "^6.4", - "symfony/dependency-injection": "^6.4", + "symfony/config": "^7", + "symfony/dependency-injection": "^7", "symfony/deprecation-contracts": "^3", "symfony/event-dispatcher-contracts": "^3", - "symfony/http-foundation": "^6.4", - "symfony/http-kernel": "^6.4", - "symfony/routing": "^6.4", - "symfony/security-bundle": "^6.4", - "symfony/security-core": "^6.4", - "symfony/security-http": "^6.4" + "symfony/http-foundation": "^7", + "symfony/http-kernel": "^7", + "symfony/routing": "^7", + "symfony/security-bundle": "^7", + "symfony/security-core": "^7", + "symfony/security-http": "^7" }, "require-dev": { "doctrine/orm": "^2.3 || ^3", - "symfony/event-dispatcher": "^6.4", - "symfony/phpunit-bridge": "^6.4" + "phpunit/phpunit": "^11", + "symfony/event-dispatcher": "^7" }, "type": "symfony-bundle", "autoload": { @@ -5401,9 +6898,9 @@ ], "support": { "issues": "https://github.com/nbgrp/onelogin-saml-bundle/issues", - "source": "https://github.com/nbgrp/onelogin-saml-bundle/tree/v1.4.0" + "source": "https://github.com/nbgrp/onelogin-saml-bundle/tree/v2.1.0" }, - "time": "2023-11-29T12:22:32+00:00" + "time": "2025-09-26T08:45:17+00:00" }, { "name": "nelexa/zip", @@ -5503,129 +7000,280 @@ "type": "symfony-bundle", "extra": { "branch-alias": { - "dev-master": "2.x-dev" + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Nelmio\\CorsBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nelmio", + "homepage": "http://nelm.io/" + }, + { + "name": "Symfony Community", + "homepage": "https://github.com/nelmio/NelmioCorsBundle/contributors" + } + ], + "description": "Adds CORS (Cross-Origin Resource Sharing) headers support in your Symfony application", + "keywords": [ + "api", + "cors", + "crossdomain" + ], + "support": { + "issues": "https://github.com/nelmio/NelmioCorsBundle/issues", + "source": "https://github.com/nelmio/NelmioCorsBundle/tree/2.5.0" + }, + "time": "2024-06-24T21:25:28+00:00" + }, + { + "name": "nelmio/security-bundle", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/nelmio/NelmioSecurityBundle.git", + "reference": "f3a7bf628a0873788172a0d05d20c0224080f5eb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nelmio/NelmioSecurityBundle/zipball/f3a7bf628a0873788172a0d05d20c0224080f5eb", + "reference": "f3a7bf628a0873788172a0d05d20c0224080f5eb", + "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", + "symfony/security-csrf": "^5.4 || ^6.3 || ^7.0", + "symfony/security-http": "^5.4 || ^6.3 || ^7.0", + "symfony/yaml": "^5.4 || ^6.3 || ^7.0", + "ua-parser/uap-php": "^3.4.4" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpstan/phpstan-symfony": "^1.1", + "phpunit/phpunit": "^9.5", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "symfony/browser-kit": "^5.4 || ^6.3 || ^7.0", + "symfony/cache": "^5.4 || ^6.3 || ^7.0", + "symfony/phpunit-bridge": "^6.3 || ^7.0", + "symfony/twig-bundle": "^5.4 || ^6.3 || ^7.0", + "twig/twig": "^2.10 || ^3.0" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Nelmio\\SecurityBundle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nelmio", + "homepage": "http://nelm.io/" + }, + { + "name": "Symfony Community", + "homepage": "https://github.com/nelmio/NelmioSecurityBundle/contributors" + } + ], + "description": "Extra security-related features for Symfony: signed/encrypted cookies, HTTPS/SSL/HSTS handling, cookie session storage, ...", + "keywords": [ + "security" + ], + "support": { + "issues": "https://github.com/nelmio/NelmioSecurityBundle/issues", + "source": "https://github.com/nelmio/NelmioSecurityBundle/tree/v3.6.0" + }, + "time": "2025-09-19T08:24:46+00:00" + }, + { + "name": "nette/schema", + "version": "v1.3.2", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "da801d52f0354f70a638673c4a0f04e16529431d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/da801d52f0354f70a638673c4a0f04e16529431d", + "reference": "da801d52f0354f70a638673c4a0f04e16529431d", + "shasum": "" + }, + "require": { + "nette/utils": "^4.0", + "php": "8.1 - 8.4" + }, + "require-dev": { + "nette/tester": "^2.5.2", + "phpstan/phpstan-nette": "^1.0", + "tracy/tracy": "^2.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" } }, "autoload": { - "psr-4": { - "Nelmio\\CorsBundle\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" ], "authors": [ { - "name": "Nelmio", - "homepage": "http://nelm.io/" + "name": "David Grudl", + "homepage": "https://davidgrudl.com/" }, { - "name": "Symfony Community", - "homepage": "https://github.com/nelmio/NelmioCorsBundle/contributors" + "name": "Nette Community", + "homepage": "https://nette.org/contributors" } ], - "description": "Adds CORS (Cross-Origin Resource Sharing) headers support in your Symfony application", + "description": "๐Ÿ“ Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org/", "keywords": [ - "api", - "cors", - "crossdomain" + "config", + "nette" ], "support": { - "issues": "https://github.com/nelmio/NelmioCorsBundle/issues", - "source": "https://github.com/nelmio/NelmioCorsBundle/tree/2.5.0" + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.3.2" }, - "time": "2024-06-24T21:25:28+00:00" + "time": "2024-10-06T23:10:23+00:00" }, { - "name": "nelmio/security-bundle", - "version": "v3.4.2", + "name": "nette/utils", + "version": "v4.0.8", "source": { "type": "git", - "url": "https://github.com/nelmio/NelmioSecurityBundle.git", - "reference": "3c4739628eafe886c001210aa0d97b33f3551599" + "url": "https://github.com/nette/utils.git", + "reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nelmio/NelmioSecurityBundle/zipball/3c4739628eafe886c001210aa0d97b33f3551599", - "reference": "3c4739628eafe886c001210aa0d97b33f3551599", + "url": "https://api.github.com/repos/nette/utils/zipball/c930ca4e3cf4f17dcfb03037703679d2396d2ede", + "reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede", "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", - "symfony/security-csrf": "^5.4 || ^6.3 || ^7.0", - "symfony/security-http": "^5.4 || ^6.3 || ^7.0", - "symfony/yaml": "^5.4 || ^6.3 || ^7.0", - "ua-parser/uap-php": "^3.4.4" + "php": "8.0 - 8.5" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" }, "require-dev": { - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-phpunit": "^1.0", - "phpstan/phpstan-strict-rules": "^1.1", - "phpstan/phpstan-symfony": "^1.1", - "phpunit/phpunit": "^9.5", - "psr/cache": "^1.0 || ^2.0 || ^3.0", - "symfony/browser-kit": "^5.4 || ^6.3 || ^7.0", - "symfony/cache": "^5.4 || ^6.3 || ^7.0", - "symfony/phpunit-bridge": "^6.3 || ^7.0", - "symfony/twig-bundle": "^5.4 || ^6.3 || ^7.0", - "twig/twig": "^2.10 || ^3.0" + "jetbrains/phpstorm-attributes": "^1.2", + "nette/tester": "^2.5", + "phpstan/phpstan-nette": "^2.0@stable", + "tracy/tracy": "^2.9" }, - "type": "symfony-bundle", + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" + }, + "type": "library", "extra": { "branch-alias": { - "dev-master": "3.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { "psr-4": { - "Nelmio\\SecurityBundle\\": "src/" - } + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" ], "authors": [ { - "name": "Nelmio", - "homepage": "http://nelm.io/" + "name": "David Grudl", + "homepage": "https://davidgrudl.com/" }, { - "name": "Symfony Community", - "homepage": "https://github.com/nelmio/NelmioSecurityBundle/contributors" + "name": "Nette Community", + "homepage": "https://nette.org/contributors" } ], - "description": "Extra security-related features for Symfony: signed/encrypted cookies, HTTPS/SSL/HSTS handling, cookie session storage, ...", + "description": "๐Ÿ›  Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org/", "keywords": [ - "security" + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" ], "support": { - "issues": "https://github.com/nelmio/NelmioSecurityBundle/issues", - "source": "https://github.com/nelmio/NelmioSecurityBundle/tree/v3.4.2" + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v4.0.8" }, - "time": "2024-09-10T13:22:26+00:00" + "time": "2025-08-06T21:43:34+00:00" }, { "name": "nikolaposa/version", - "version": "4.2.0", + "version": "4.2.1", "source": { "type": "git", "url": "https://github.com/nikolaposa/version.git", - "reference": "003fefa14f47cd44917546285e39d196af062a95" + "reference": "2b9ee2f0b09333b6ce00bd6b63132cdf1d7a1428" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikolaposa/version/zipball/003fefa14f47cd44917546285e39d196af062a95", - "reference": "003fefa14f47cd44917546285e39d196af062a95", + "url": "https://api.github.com/repos/nikolaposa/version/zipball/2b9ee2f0b09333b6ce00bd6b63132cdf1d7a1428", + "reference": "2b9ee2f0b09333b6ce00bd6b63132cdf1d7a1428", "shasum": "" }, "require": { @@ -5671,9 +7319,9 @@ ], "support": { "issues": "https://github.com/nikolaposa/version/issues", - "source": "https://github.com/nikolaposa/version/tree/4.2.0" + "source": "https://github.com/nikolaposa/version/tree/4.2.1" }, - "time": "2023-12-29T22:07:54+00:00" + "time": "2025-03-24T19:12:02+00:00" }, { "name": "nyholm/psr7", @@ -5755,23 +7403,24 @@ }, { "name": "omines/datatables-bundle", - "version": "0.9.1", + "version": "0.10.3", "source": { "type": "git", "url": "https://github.com/omines/datatables-bundle.git", - "reference": "259ad042c736f4667111af26193182665ae0ed4f" + "reference": "d64e7d5c72303995ada7365b467166f3cdf4757c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/omines/datatables-bundle/zipball/259ad042c736f4667111af26193182665ae0ed4f", - "reference": "259ad042c736f4667111af26193182665ae0ed4f", + "url": "https://api.github.com/repos/omines/datatables-bundle/zipball/d64e7d5c72303995ada7365b467166f3cdf4757c", + "reference": "d64e7d5c72303995ada7365b467166f3cdf4757c", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/event-dispatcher": "^6.4|^7.1", "symfony/framework-bundle": "^6.4|^7.1", "symfony/options-resolver": "^6.4|^7.1", + "symfony/polyfill-mbstring": "^1.31.0", "symfony/property-access": "^6.4|^7.1", "symfony/translation": "^6.4|^7.1" }, @@ -5779,39 +7428,38 @@ "doctrine/orm": "^3.0 <3.3" }, "require-dev": { - "doctrine/common": "^3.4.5", - "doctrine/doctrine-bundle": "^2.13.1", - "doctrine/orm": "^2.19.3|^3.3.0", - "doctrine/persistence": "^3.4.0", + "doctrine/common": "^3.5.0", + "doctrine/doctrine-bundle": "^2.15.0", + "doctrine/orm": "^2.19.3|^3.4.1", + "doctrine/persistence": "^3.4.0|^4.0.0", "ext-curl": "*", "ext-json": "*", "ext-mbstring": "*", "ext-mongodb": "*", "ext-pdo_sqlite": "*", "ext-zip": "*", - "friendsofphp/php-cs-fixer": "^3.65.0", - "mongodb/mongodb": "^1.20.0", + "friendsofphp/php-cs-fixer": "^3.75.0", + "mongodb/mongodb": "^1.20.0|^2.1.0", "ocramius/package-versions": "^2.9", "openspout/openspout": "^4.23", - "phpoffice/phpspreadsheet": "^2.3.3|^3.5", + "phpoffice/phpspreadsheet": "^2.3.3|^3.9.2|^4.4.0", "phpstan/extension-installer": "^1.4.3", - "phpstan/phpstan": "^2.0.3", - "phpstan/phpstan-doctrine": "^2.0.1", - "phpstan/phpstan-phpunit": "^2.0.1", - "phpstan/phpstan-symfony": "^2.0.0", - "phpunit/phpunit": "^10.5.38|^11.4.4", - "ruflin/elastica": "^6.2|^7.3.2", - "symfony/browser-kit": "^6.4.13|^7.1", - "symfony/css-selector": "^6.4.13|^7.1", - "symfony/doctrine-bridge": "^6.4.13|^7.1", - "symfony/dom-crawler": "^6.4.13|^7.1", - "symfony/intl": "^6.4.13|^7.1", - "symfony/mime": "^6.4.13|^7.1", - "symfony/phpunit-bridge": "^7.2", - "symfony/polyfill-mbstring": "^1.31.0", - "symfony/twig-bundle": "^6.4|^7.1", - "symfony/var-dumper": "^6.4.13|^7.1", - "symfony/yaml": "^6.4.13|^7.1" + "phpstan/phpstan": "^2.1.17", + "phpstan/phpstan-doctrine": "^2.0.3", + "phpstan/phpstan-phpunit": "^2.0.6", + "phpstan/phpstan-symfony": "^2.0.6", + "phpunit/phpunit": "^10.5.38|^11.5.24", + "ruflin/elastica": "^7.3.2", + "symfony/browser-kit": "^6.4.13|^7.3", + "symfony/css-selector": "^6.4.13|^7.3", + "symfony/doctrine-bridge": "^6.4.13|^7.3", + "symfony/dom-crawler": "^6.4.13|^7.3", + "symfony/intl": "^6.4.13|^7.3", + "symfony/mime": "^6.4.13|^7.3", + "symfony/phpunit-bridge": "^7.3", + "symfony/twig-bundle": "^6.4|^7.3", + "symfony/var-dumper": "^6.4.13|^7.3", + "symfony/yaml": "^6.4.13|^7.3" }, "suggest": { "doctrine/doctrine-bundle": "For integrated access to Doctrine object managers", @@ -5825,7 +7473,7 @@ "type": "symfony-bundle", "extra": { "branch-alias": { - "dev-master": "0.9-dev" + "dev-master": "0.10-dev" } }, "autoload": { @@ -5863,7 +7511,7 @@ ], "support": { "issues": "https://github.com/omines/datatables-bundle/issues", - "source": "https://github.com/omines/datatables-bundle/tree/0.9.1" + "source": "https://github.com/omines/datatables-bundle/tree/0.10.3" }, "funding": [ { @@ -5871,20 +7519,20 @@ "type": "github" } ], - "time": "2024-12-05T00:10:46+00:00" + "time": "2025-07-24T19:50:46+00:00" }, { "name": "onelogin/php-saml", - "version": "4.2.0", + "version": "4.3.0", "source": { "type": "git", "url": "https://github.com/SAML-Toolkits/php-saml.git", - "reference": "d3b5172f137db2f412239432d77253ceaaa1e939" + "reference": "bf5efce9f2df5d489d05e78c27003a0fc8bc50f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SAML-Toolkits/php-saml/zipball/d3b5172f137db2f412239432d77253ceaaa1e939", - "reference": "d3b5172f137db2f412239432d77253ceaaa1e939", + "url": "https://api.github.com/repos/SAML-Toolkits/php-saml/zipball/bf5efce9f2df5d489d05e78c27003a0fc8bc50f0", + "reference": "bf5efce9f2df5d489d05e78c27003a0fc8bc50f0", "shasum": "" }, "require": { @@ -5935,28 +7583,30 @@ "type": "github" } ], - "time": "2024-05-30T15:10:40+00:00" + "time": "2025-05-25T14:28:00+00:00" }, { "name": "paragonie/constant_time_encoding", - "version": "v2.7.0", + "version": "v3.1.3", "source": { "type": "git", "url": "https://github.com/paragonie/constant_time_encoding.git", - "reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105" + "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/52a0d99e69f56b9ec27ace92ba56897fe6993105", - "reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", + "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", "shasum": "" }, "require": { - "php": "^7|^8" + "php": "^8" }, "require-dev": { - "phpunit/phpunit": "^6|^7|^8|^9", - "vimeo/psalm": "^1|^2|^3|^4" + "infection/infection": "^0", + "nikic/php-fuzzer": "^0", + "phpunit/phpunit": "^9|^10|^11", + "vimeo/psalm": "^4|^5|^6" }, "type": "library", "autoload": { @@ -6002,7 +7652,7 @@ "issues": "https://github.com/paragonie/constant_time_encoding/issues", "source": "https://github.com/paragonie/constant_time_encoding" }, - "time": "2024-05-08T12:18:48+00:00" + "time": "2025-09-24T15:06:41+00:00" }, { "name": "paragonie/random_compat", @@ -6056,16 +7706,16 @@ }, { "name": "paragonie/sodium_compat", - "version": "v1.21.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/paragonie/sodium_compat.git", - "reference": "bb312875dcdd20680419564fe42ba1d9564b9e37" + "reference": "b938a5c6844d222a26d46a6c7b80291e4cd8cfab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/bb312875dcdd20680419564fe42ba1d9564b9e37", - "reference": "bb312875dcdd20680419564fe42ba1d9564b9e37", + "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/b938a5c6844d222a26d46a6c7b80291e4cd8cfab", + "reference": "b938a5c6844d222a26d46a6c7b80291e4cd8cfab", "shasum": "" }, "require": { @@ -6136,22 +7786,99 @@ ], "support": { "issues": "https://github.com/paragonie/sodium_compat/issues", - "source": "https://github.com/paragonie/sodium_compat/tree/v1.21.1" + "source": "https://github.com/paragonie/sodium_compat/tree/v1.23.0" + }, + "time": "2025-10-06T08:53:07+00:00" + }, + { + "name": "part-db/exchanger", + "version": "v3.1.0", + "source": { + "type": "git", + "url": "https://github.com/Part-DB/exchanger.git", + "reference": "a43fe79a082e331ec2b24f3579e4fba153743757" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Part-DB/exchanger/zipball/a43fe79a082e331ec2b24f3579e4fba153743757", + "reference": "a43fe79a082e331ec2b24f3579e4fba153743757", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-libxml": "*", + "ext-simplexml": "*", + "php": "^7.1.3 || ^8.0", + "php-http/client-implementation": "^1.0", + "php-http/discovery": "^1.6", + "php-http/httplug": "^1.0 || ^2.0", + "psr/http-factory": "^1.0.2", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "nyholm/psr7": "^1.0", + "php-http/message": "^1.7", + "php-http/message-factory": "^1.1", + "php-http/mock-client": "^1.0", + "phpunit/phpunit": "^7 || ^8 || ^9.4 || ^10.5", + "symfony/http-client": "^5.4 || ^6.4 || ^7.0" + }, + "suggest": { + "php-http/guzzle6-adapter": "Required to use Guzzle for sending HTTP requests", + "php-http/message": "Required to use Guzzle for sending HTTP requests" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Exchanger\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florian Voutzinos", + "email": "florian@voutzinos.com", + "homepage": "https://voutzinos.com/" + }, + { + "name": "Jan Bรถhmer", + "email": "mail@jan-boehmer.de" + } + ], + "description": "Fork of florianv/exchanger, a library to convert currencies using different exchange rate providers. Modernized to be compatible with Part-DB.", + "homepage": "https://github.com/Part-DB/exchanger", + "keywords": [ + "Rate", + "conversion", + "currency", + "exchange rates", + "money" + ], + "support": { + "source": "https://github.com/Part-DB/exchanger/tree/v3.1.0" }, - "time": "2024-04-22T22:05:04+00:00" + "time": "2025-09-05T19:48:23+00:00" }, { "name": "part-db/label-fonts", - "version": "v1.1.0", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/Part-DB/label-fonts.git", - "reference": "77c84b70ed3bb005df15f30ff835ddec490394b9" + "reference": "c85aeb051d6492961a2c59bc291979f15ce60e88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Part-DB/label-fonts/zipball/77c84b70ed3bb005df15f30ff835ddec490394b9", - "reference": "77c84b70ed3bb005df15f30ff835ddec490394b9", + "url": "https://api.github.com/repos/Part-DB/label-fonts/zipball/c85aeb051d6492961a2c59bc291979f15ce60e88", + "reference": "c85aeb051d6492961a2c59bc291979f15ce60e88", "shasum": "" }, "type": "library", @@ -6168,15 +7895,158 @@ "description": "This library bundles the fonts used in Part-DB for label generators. Fonts are work of others.", "homepage": "https://github.com/Part-DB/Part-DB-server", "keywords": [ - "font", - "fonts", - "part-db" + "font", + "fonts", + "part-db" + ], + "support": { + "issues": "https://github.com/Part-DB/label-fonts/issues", + "source": "https://github.com/Part-DB/label-fonts/tree/v1.2.0" + }, + "time": "2025-09-07T15:42:51+00:00" + }, + { + "name": "part-db/swap", + "version": "v5.0.0", + "source": { + "type": "git", + "url": "https://github.com/Part-DB/swap.git", + "reference": "4fa57dec2eb1cbe0f6b8c92a2c250ecbe80688fe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Part-DB/swap/zipball/4fa57dec2eb1cbe0f6b8c92a2c250ecbe80688fe", + "reference": "4fa57dec2eb1cbe0f6b8c92a2c250ecbe80688fe", + "shasum": "" + }, + "require": { + "part-db/exchanger": "^3.0", + "php": "^7.1.3 || ^8.0", + "php-http/message-factory": "^1.1" + }, + "require-dev": { + "nyholm/psr7": "^1.0", + "php-http/discovery": "^1.0", + "php-http/message": "^1.7", + "php-http/mock-client": "^1.0", + "phpunit/phpunit": "^7 || ^8 || ^9", + "symfony/http-client": "^5.4||^6.0||^7.0" + }, + "suggest": { + "php-http/discovery": "If you are not using `useHttpClient` but instead want to auto-discover HttpClient" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "psr-4": { + "Swap\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florian Voutzinos", + "email": "florian@voutzinos.com", + "homepage": "https://voutzinos.com/" + }, + { + "name": "Jan Bรถhmer", + "email": "mail@jan-boehmer.de" + } + ], + "description": "Fork of florianv/swap modernized for use in Part-DB. Exchange rates library for PHP", + "keywords": [ + "Rate", + "conversion", + "currency", + "exchange rates", + "money" + ], + "support": { + "source": "https://github.com/Part-DB/swap/tree/v5.0.0" + }, + "time": "2025-09-05T17:10:01+00:00" + }, + { + "name": "part-db/swap-bundle", + "version": "v6.1.0", + "source": { + "type": "git", + "url": "https://github.com/Part-DB/symfony-swap.git", + "reference": "fd78ebfbd762b1d76b4d71f713f39add63dec62b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Part-DB/symfony-swap/zipball/fd78ebfbd762b1d76b4d71f713f39add63dec62b", + "reference": "fd78ebfbd762b1d76b4d71f713f39add63dec62b", + "shasum": "" + }, + "require": { + "part-db/exchanger": "^3.1.0", + "part-db/swap": "^5.0", + "php": "^7.1.3|^8.0", + "psr/http-client": "^1.0", + "symfony/framework-bundle": "~3.0|~4.0|~5.0|~6.0|~7.0" + }, + "require-dev": { + "nyholm/psr7": "^1.1", + "php-http/guzzle6-adapter": "^1.0", + "php-http/message": "^1.7", + "phpunit/phpunit": "~5.7|~6.0|~7.0|~8.0|~9.0", + "symfony/cache": "~3.0|~4.0|~5.0|~6.0|~7.0", + "symfony/http-client": "~7.0|~6.0|~5.0" + }, + "suggest": { + "symfony/cache": "For caching" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "Florianv\\SwapBundle\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florian Voutzinos", + "email": "florian@voutzinos.com", + "homepage": "http://florian.voutzinos.com/" + }, + { + "name": "Jan Bรถhmer", + "email": "mail@jan-boehmer.de" + } + ], + "description": "Fork of florianv/swap-bundle, modernized for use with Part-DB. Integrates the Swap library with Symfony", + "homepage": "https://github.com/florianv/FlorianvSwapBundle", + "keywords": [ + "Rate", + "bundle", + "conversion", + "currency", + "exchange", + "money", + "symfony" ], "support": { - "issues": "https://github.com/Part-DB/label-fonts/issues", - "source": "https://github.com/Part-DB/label-fonts/tree/v1.1.0" + "source": "https://github.com/Part-DB/symfony-swap/tree/v6.1.0" }, - "time": "2024-02-08T21:44:38+00:00" + "time": "2025-09-05T19:52:56+00:00" }, { "name": "php-http/discovery", @@ -6314,6 +8184,61 @@ }, "time": "2024-09-23T11:39:58+00:00" }, + { + "name": "php-http/message-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/message-factory.git", + "reference": "4d8778e1c7d405cbb471574821c1ff5b68cc8f57" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/message-factory/zipball/4d8778e1c7d405cbb471574821c1ff5b68cc8f57", + "reference": "4d8778e1c7d405cbb471574821c1ff5b68cc8f57", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mรกrk Sรกgi-Kazรกr", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Factory interfaces for PSR-7 HTTP Message", + "homepage": "http://php-http.org/", + "keywords": [ + "factory", + "http", + "message", + "stream", + "uri" + ], + "support": { + "issues": "https://github.com/php-http/message-factory/issues", + "source": "https://github.com/php-http/message-factory/tree/1.1.0" + }, + "abandoned": "psr/http-factory", + "time": "2023-04-14T14:16:17+00:00" + }, { "name": "php-http/promise", "version": "1.3.1", @@ -6421,16 +8346,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.6.1", + "version": "5.6.3", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8" + "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8", - "reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94f8051919d1b0369a6bcc7931d679a511c03fe9", + "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9", "shasum": "" }, "require": { @@ -6479,9 +8404,9 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.1" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.3" }, - "time": "2024-12-07T09:39:29+00:00" + "time": "2025-08-01T19:43:32+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -6541,18 +8466,124 @@ }, "time": "2024-11-09T15:12:26+00:00" }, + { + "name": "phpoffice/phpspreadsheet", + "version": "5.1.0", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "fd26e45a814e94ae2aad0df757d9d1739c4bf2e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fd26e45a814e94ae2aad0df757d9d1739c4bf2e0", + "reference": "fd26e45a814e94ae2aad0df757d9d1739c4bf2e0", + "shasum": "" + }, + "require": { + "composer/pcre": "^1||^2||^3", + "ext-ctype": "*", + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "maennchen/zipstream-php": "^2.1 || ^3.0", + "markbaker/complex": "^3.0", + "markbaker/matrix": "^3.0", + "php": "^8.1", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-main", + "dompdf/dompdf": "^2.0 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.2", + "mitoteam/jpgraph": "^10.3", + "mpdf/mpdf": "^8.1.1", + "phpcompatibility/php-compatibility": "^9.3", + "phpstan/phpstan": "^1.1 || ^2.0", + "phpstan/phpstan-deprecation-rules": "^1.0 || ^2.0", + "phpstan/phpstan-phpunit": "^1.0 || ^2.0", + "phpunit/phpunit": "^10.5", + "squizlabs/php_codesniffer": "^3.7", + "tecnickcom/tcpdf": "^6.5" + }, + "suggest": { + "dompdf/dompdf": "Option for rendering PDF with PDF Writer", + "ext-intl": "PHP Internationalization Functions", + "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be/" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net/" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net/" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + } + ], + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "keywords": [ + "OpenXML", + "excel", + "gnumeric", + "ods", + "php", + "spreadsheet", + "xls", + "xlsx" + ], + "support": { + "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/5.1.0" + }, + "time": "2025-09-04T05:34:49+00:00" + }, { "name": "phpstan/phpdoc-parser", - "version": "2.0.0", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "c00d78fb6b29658347f9d37ebe104bffadf36299" + "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/c00d78fb6b29658347f9d37ebe104bffadf36299", - "reference": "c00d78fb6b29658347f9d37ebe104bffadf36299", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/1e0cd5370df5dd2e556a36b9c62f62e555870495", + "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495", "shasum": "" }, "require": { @@ -6584,9 +8615,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/2.0.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.0" }, - "time": "2024-10-13T11:29:49+00:00" + "time": "2025-08-30T15:50:23+00:00" }, { "name": "psr/cache", @@ -6897,16 +8928,16 @@ }, { "name": "psr/http-message", - "version": "2.0", + "version": "1.1", "source": { "type": "git", "url": "https://github.com/php-fig/http-message.git", - "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", - "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", "shasum": "" }, "require": { @@ -6915,7 +8946,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "1.1.x-dev" } }, "autoload": { @@ -6930,7 +8961,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "homepage": "http://www.php-fig.org/" } ], "description": "Common interface for HTTP messages", @@ -6944,9 +8975,9 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-message/tree/2.0" + "source": "https://github.com/php-fig/http-message/tree/1.1" }, - "time": "2023-04-04T09:54:51+00:00" + "time": "2023-04-04T09:50:52+00:00" }, { "name": "psr/link", @@ -7151,16 +9182,16 @@ }, { "name": "revolt/event-loop", - "version": "v1.0.6", + "version": "v1.0.7", "source": { "type": "git", "url": "https://github.com/revoltphp/event-loop.git", - "reference": "25de49af7223ba039f64da4ae9a28ec2d10d0254" + "reference": "09bf1bf7f7f574453efe43044b06fafe12216eb3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/25de49af7223ba039f64da4ae9a28ec2d10d0254", - "reference": "25de49af7223ba039f64da4ae9a28ec2d10d0254", + "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/09bf1bf7f7f574453efe43044b06fafe12216eb3", + "reference": "09bf1bf7f7f574453efe43044b06fafe12216eb3", "shasum": "" }, "require": { @@ -7217,9 +9248,54 @@ ], "support": { "issues": "https://github.com/revoltphp/event-loop/issues", - "source": "https://github.com/revoltphp/event-loop/tree/v1.0.6" + "source": "https://github.com/revoltphp/event-loop/tree/v1.0.7" + }, + "time": "2025-01-25T19:27:39+00:00" + }, + { + "name": "rhukster/dom-sanitizer", + "version": "1.0.7", + "source": { + "type": "git", + "url": "https://github.com/rhukster/dom-sanitizer.git", + "reference": "c2a98f27ad742668b254282ccc5581871d0fb601" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rhukster/dom-sanitizer/zipball/c2a98f27ad742668b254282ccc5581871d0fb601", + "reference": "c2a98f27ad742668b254282ccc5581871d0fb601", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9" + }, + "type": "library", + "autoload": { + "psr-4": { + "Rhukster\\DomSanitizer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andy Miller", + "email": "rhuk@rhuk.net" + } + ], + "description": "A simple but effective DOM/SVG/MathML Sanitizer for PHP 7.4+", + "support": { + "issues": "https://github.com/rhukster/dom-sanitizer/issues", + "source": "https://github.com/rhukster/dom-sanitizer/tree/1.0.7" }, - "time": "2023-11-30T05:34:44+00:00" + "time": "2023-11-06T16:46:48+00:00" }, { "name": "robrichards/xmlseclibs", @@ -7405,16 +9481,16 @@ }, { "name": "s9e/text-formatter", - "version": "2.18.0", + "version": "2.19.0", "source": { "type": "git", "url": "https://github.com/s9e/TextFormatter.git", - "reference": "4970711f25d94306b4835b723b9cc5010170ea37" + "reference": "d65a4f61cbe494937afb3150dc73b6e757d400d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/4970711f25d94306b4835b723b9cc5010170ea37", - "reference": "4970711f25d94306b4835b723b9cc5010170ea37", + "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/d65a4f61cbe494937afb3150dc73b6e757d400d3", + "reference": "d65a4f61cbe494937afb3150dc73b6e757d400d3", "shasum": "" }, "require": { @@ -7442,7 +9518,7 @@ }, "type": "library", "extra": { - "version": "2.18.0" + "version": "2.19.0" }, "autoload": { "psr-4": { @@ -7474,22 +9550,22 @@ ], "support": { "issues": "https://github.com/s9e/TextFormatter/issues", - "source": "https://github.com/s9e/TextFormatter/tree/2.18.0" + "source": "https://github.com/s9e/TextFormatter/tree/2.19.0" }, - "time": "2024-07-24T14:50:52+00:00" + "time": "2025-04-26T09:27:34+00:00" }, { "name": "sabberworm/php-css-parser", - "version": "v8.7.0", + "version": "v8.9.0", "source": { "type": "git", "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", - "reference": "f414ff953002a9b18e3a116f5e462c56f21237cf" + "reference": "d8e916507b88e389e26d4ab03c904a082aa66bb9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/f414ff953002a9b18e3a116f5e462c56f21237cf", - "reference": "f414ff953002a9b18e3a116f5e462c56f21237cf", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/d8e916507b88e389e26d4ab03c904a082aa66bb9", + "reference": "d8e916507b88e389e26d4ab03c904a082aa66bb9", "shasum": "" }, "require": { @@ -7497,7 +9573,8 @@ "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 || 6.5.14 || 7.5.20 || 8.5.40" + "phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.41", + "rawr/cross-data-providers": "^2.0.0" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" @@ -7539,26 +9616,26 @@ ], "support": { "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", - "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.7.0" + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.9.0" }, - "time": "2024-10-27T17:38:32+00:00" + "time": "2025-07-11T13:20:48+00:00" }, { "name": "scheb/2fa-backup-code", - "version": "v6.12.0", + "version": "v7.11.0", "source": { "type": "git", "url": "https://github.com/scheb/2fa-backup-code.git", - "reference": "1ad84e7eb26eb425c609e03097cac99387dde44c" + "reference": "62c6099b179903db5ab03b8059068cdb28659294" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/scheb/2fa-backup-code/zipball/1ad84e7eb26eb425c609e03097cac99387dde44c", - "reference": "1ad84e7eb26eb425c609e03097cac99387dde44c", + "url": "https://api.github.com/repos/scheb/2fa-backup-code/zipball/62c6099b179903db5ab03b8059068cdb28659294", + "reference": "62c6099b179903db5ab03b8059068cdb28659294", "shasum": "" }, "require": { - "php": "~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0", "scheb/2fa-bundle": "self.version" }, "type": "library", @@ -7588,40 +9665,40 @@ "two-step" ], "support": { - "source": "https://github.com/scheb/2fa-backup-code/tree/v6.12.0" + "source": "https://github.com/scheb/2fa-backup-code/tree/v7.11.0" }, - "time": "2023-12-03T15:44:26+00:00" + "time": "2025-04-20T08:27:40+00:00" }, { "name": "scheb/2fa-bundle", - "version": "v6.12.0", + "version": "v7.11.0", "source": { "type": "git", "url": "https://github.com/scheb/2fa-bundle.git", - "reference": "6e51477c53070f27ac3e3d36be1a991870db415a" + "reference": "06a343d14dad8cdd1670157d384738f9cfba29e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/scheb/2fa-bundle/zipball/6e51477c53070f27ac3e3d36be1a991870db415a", - "reference": "6e51477c53070f27ac3e3d36be1a991870db415a", + "url": "https://api.github.com/repos/scheb/2fa-bundle/zipball/06a343d14dad8cdd1670157d384738f9cfba29e5", + "reference": "06a343d14dad8cdd1670157d384738f9cfba29e5", "shasum": "" }, "require": { "ext-json": "*", - "php": "~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0", - "symfony/config": "^5.4 || ^6.0", - "symfony/dependency-injection": "^5.4 || ^6.0", - "symfony/event-dispatcher": "^5.4 || ^6.0", - "symfony/framework-bundle": "^5.4 || ^6.0", - "symfony/http-foundation": "^5.4 || ^6.0", - "symfony/http-kernel": "^5.4 || ^6.0", - "symfony/property-access": "^5.4 || ^6.0", - "symfony/security-bundle": "^5.4 || ^6.0", - "symfony/twig-bundle": "^5.4 || ^6.0" + "php": "~8.2.0 || ~8.3.0 || ~8.4.0", + "symfony/config": "^6.4 || ^7.0", + "symfony/dependency-injection": "^6.4 || ^7.0", + "symfony/event-dispatcher": "^6.4 || ^7.0", + "symfony/framework-bundle": "^6.4 || ^7.0", + "symfony/http-foundation": "^6.4 || ^7.0", + "symfony/http-kernel": "^6.4 || ^7.0", + "symfony/property-access": "^6.4 || ^7.0", + "symfony/security-bundle": "^6.4 || ^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/twig-bundle": "^6.4 || ^7.0" }, "conflict": { - "scheb/two-factor-bundle": "*", - "symfony/security-core": "^7" + "scheb/two-factor-bundle": "*" }, "suggest": { "scheb/2fa-backup-code": "Emergency codes when you have no access to other methods", @@ -7656,29 +9733,28 @@ "two-step" ], "support": { - "source": "https://github.com/scheb/2fa-bundle/tree/v6.12.0" + "source": "https://github.com/scheb/2fa-bundle/tree/v7.11.0" }, - "time": "2023-12-03T16:02:15+00:00" + "time": "2025-06-27T12:14:20+00:00" }, { "name": "scheb/2fa-google-authenticator", - "version": "v6.12.0", + "version": "v7.11.0", "source": { "type": "git", "url": "https://github.com/scheb/2fa-google-authenticator.git", - "reference": "2c43bbe432fdc465d8f1d1b2d73ca9ea5276fe34" + "reference": "01a446eb68a76c3d0528a190029afa5e6ce5c384" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/scheb/2fa-google-authenticator/zipball/2c43bbe432fdc465d8f1d1b2d73ca9ea5276fe34", - "reference": "2c43bbe432fdc465d8f1d1b2d73ca9ea5276fe34", + "url": "https://api.github.com/repos/scheb/2fa-google-authenticator/zipball/01a446eb68a76c3d0528a190029afa5e6ce5c384", + "reference": "01a446eb68a76c3d0528a190029afa5e6ce5c384", "shasum": "" }, "require": { - "paragonie/constant_time_encoding": "^2.4", - "php": "~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0", "scheb/2fa-bundle": "self.version", - "spomky-labs/otphp": "^10.0 || ^11.0" + "spomky-labs/otphp": "^11.0" }, "type": "library", "autoload": { @@ -7707,28 +9783,28 @@ "two-step" ], "support": { - "source": "https://github.com/scheb/2fa-google-authenticator/tree/v6.12.0" + "source": "https://github.com/scheb/2fa-google-authenticator/tree/v7.11.0" }, - "time": "2023-12-03T15:44:26+00:00" + "time": "2025-04-20T08:38:44+00:00" }, { "name": "scheb/2fa-trusted-device", - "version": "v6.12.0", + "version": "v7.11.0", "source": { "type": "git", "url": "https://github.com/scheb/2fa-trusted-device.git", - "reference": "1ca6158dc6518ca9dba8b111bd9807a8b9be2903" + "reference": "6ab98fdee3aa001ca6598eeb422d9abf2c85b5b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/scheb/2fa-trusted-device/zipball/1ca6158dc6518ca9dba8b111bd9807a8b9be2903", - "reference": "1ca6158dc6518ca9dba8b111bd9807a8b9be2903", + "url": "https://api.github.com/repos/scheb/2fa-trusted-device/zipball/6ab98fdee3aa001ca6598eeb422d9abf2c85b5b3", + "reference": "6ab98fdee3aa001ca6598eeb422d9abf2c85b5b3", "shasum": "" }, "require": { - "lcobucci/clock": "^2.0 || ^3.0", - "lcobucci/jwt": "^4.1 || ^5.0", - "php": "~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0", + "lcobucci/clock": "^3.0", + "lcobucci/jwt": "^5.0", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0", "scheb/2fa-bundle": "self.version" }, "type": "library", @@ -7758,9 +9834,9 @@ "two-step" ], "support": { - "source": "https://github.com/scheb/2fa-trusted-device/tree/v6.12.0" + "source": "https://github.com/scheb/2fa-trusted-device/tree/v7.11.0" }, - "time": "2023-12-03T15:44:26+00:00" + "time": "2025-06-27T12:14:20+00:00" }, { "name": "shivas/versioning-bundle", @@ -7824,16 +9900,16 @@ }, { "name": "spatie/db-dumper", - "version": "3.7.1", + "version": "3.8.0", "source": { "type": "git", "url": "https://github.com/spatie/db-dumper.git", - "reference": "55d4d6710e1ab18c1e7ce2b22b8ad4bea2a30016" + "reference": "91e1fd4dc000aefc9753cda2da37069fc996baee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/db-dumper/zipball/55d4d6710e1ab18c1e7ce2b22b8ad4bea2a30016", - "reference": "55d4d6710e1ab18c1e7ce2b22b8ad4bea2a30016", + "url": "https://api.github.com/repos/spatie/db-dumper/zipball/91e1fd4dc000aefc9753cda2da37069fc996baee", + "reference": "91e1fd4dc000aefc9753cda2da37069fc996baee", "shasum": "" }, "require": { @@ -7871,7 +9947,7 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/db-dumper/tree/3.7.1" + "source": "https://github.com/spatie/db-dumper/tree/3.8.0" }, "funding": [ { @@ -7883,41 +9959,41 @@ "type": "github" } ], - "time": "2024-11-18T14:54:31+00:00" + "time": "2025-02-14T15:04:22+00:00" }, { "name": "spomky-labs/cbor-php", - "version": "3.1.0", + "version": "3.1.1", "source": { "type": "git", "url": "https://github.com/Spomky-Labs/cbor-php.git", - "reference": "499d9bff0a6d59c4f1b813cc617fc3fd56d6dca4" + "reference": "5404f3e21cbe72f5cf612aa23db2b922fd2f43bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/499d9bff0a6d59c4f1b813cc617fc3fd56d6dca4", - "reference": "499d9bff0a6d59c4f1b813cc617fc3fd56d6dca4", + "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/5404f3e21cbe72f5cf612aa23db2b922fd2f43bf", + "reference": "5404f3e21cbe72f5cf612aa23db2b922fd2f43bf", "shasum": "" }, "require": { - "brick/math": "^0.9|^0.10|^0.11|^0.12", + "brick/math": "^0.9|^0.10|^0.11|^0.12|^0.13", "ext-mbstring": "*", "php": ">=8.0" }, "require-dev": { - "ekino/phpstan-banned-code": "^1.0", + "deptrac/deptrac": "^3.0", + "ekino/phpstan-banned-code": "^1.0|^2.0|^3.0", "ext-json": "*", "infection/infection": "^0.29", "php-parallel-lint/php-parallel-lint": "^1.3", "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.0", - "phpstan/phpstan-beberlei-assert": "^1.0", - "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-phpunit": "^1.0", - "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/phpunit": "^10.1|^11.0", - "qossmic/deptrac": "^2.0", - "rector/rector": "^1.0", + "phpstan/phpstan": "^1.0|^2.0", + "phpstan/phpstan-beberlei-assert": "^1.0|^2.0", + "phpstan/phpstan-deprecation-rules": "^1.0|^2.0", + "phpstan/phpstan-phpunit": "^1.0|^2.0", + "phpstan/phpstan-strict-rules": "^1.0|^2.0", + "phpunit/phpunit": "^10.1|^11.0|^12.0", + "rector/rector": "^1.0|^2.0", "roave/security-advisories": "dev-latest", "symfony/var-dumper": "^6.0|^7.0", "symplify/easy-coding-standard": "^12.0" @@ -7954,7 +10030,7 @@ ], "support": { "issues": "https://github.com/Spomky-Labs/cbor-php/issues", - "source": "https://github.com/Spomky-Labs/cbor-php/tree/3.1.0" + "source": "https://github.com/Spomky-Labs/cbor-php/tree/3.1.1" }, "funding": [ { @@ -7966,7 +10042,7 @@ "type": "patreon" } ], - "time": "2024-07-18T08:37:03+00:00" + "time": "2025-06-13T11:57:55+00:00" }, { "name": "spomky-labs/otphp", @@ -8052,20 +10128,20 @@ }, { "name": "spomky-labs/pki-framework", - "version": "1.2.2", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/Spomky-Labs/pki-framework.git", - "reference": "5ac374c3e295c8b917208ff41b4d30f76668478c" + "reference": "eced5b5ce70518b983ff2be486e902bbd15135ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/5ac374c3e295c8b917208ff41b4d30f76668478c", - "reference": "5ac374c3e295c8b917208ff41b4d30f76668478c", + "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/eced5b5ce70518b983ff2be486e902bbd15135ae", + "reference": "eced5b5ce70518b983ff2be486e902bbd15135ae", "shasum": "" }, "require": { - "brick/math": "^0.10|^0.11|^0.12", + "brick/math": "^0.10|^0.11|^0.12|^0.13", "ext-mbstring": "*", "php": ">=8.1" }, @@ -8080,7 +10156,7 @@ "phpstan/phpstan-deprecation-rules": "^1.0|^2.0", "phpstan/phpstan-phpunit": "^1.1|^2.0", "phpstan/phpstan-strict-rules": "^1.3|^2.0", - "phpunit/phpunit": "^10.1|^11.0", + "phpunit/phpunit": "^10.1|^11.0|^12.0", "rector/rector": "^1.0|^2.0", "roave/security-advisories": "dev-latest", "symfony/string": "^6.4|^7.0", @@ -8145,7 +10221,7 @@ ], "support": { "issues": "https://github.com/Spomky-Labs/pki-framework/issues", - "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.2.2" + "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.3.0" }, "funding": [ { @@ -8157,7 +10233,7 @@ "type": "patreon" } ], - "time": "2025-01-03T09:35:48+00:00" + "time": "2025-06-13T08:35:04+00:00" }, { "name": "symfony/apache-pack", @@ -8187,28 +10263,28 @@ }, { "name": "symfony/asset", - "version": "v6.4.13", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/asset.git", - "reference": "2466c17d61d14539cddf77e57ebb9cc971185302" + "reference": "56c4d9f759247c4e07d8549e3baf7493cb9c3e4b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/asset/zipball/2466c17d61d14539cddf77e57ebb9cc971185302", - "reference": "2466c17d61d14539cddf77e57ebb9cc971185302", + "url": "https://api.github.com/repos/symfony/asset/zipball/56c4d9f759247c4e07d8549e3baf7493cb9c3e4b", + "reference": "56c4d9f759247c4e07d8549e3baf7493cb9c3e4b", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "conflict": { - "symfony/http-foundation": "<5.4" + "symfony/http-foundation": "<6.4" }, "require-dev": { - "symfony/http-client": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^5.4|^6.0|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0" + "symfony/http-client": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -8236,7 +10312,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.13" + "source": "https://github.com/symfony/asset/tree/v7.3.0" }, "funding": [ { @@ -8252,35 +10328,36 @@ "type": "tidelift" } ], - "time": "2024-10-25T15:07:50+00:00" + "time": "2025-03-05T10:15:41+00:00" }, { "name": "symfony/cache", - "version": "v6.4.16", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "70d60e9a3603108563010f8592dff15a6f15dfae" + "reference": "bf8afc8ffd4bfd3d9c373e417f041d9f1e5b863f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/70d60e9a3603108563010f8592dff15a6f15dfae", - "reference": "70d60e9a3603108563010f8592dff15a6f15dfae", + "url": "https://api.github.com/repos/symfony/cache/zipball/bf8afc8ffd4bfd3d9c373e417f041d9f1e5b863f", + "reference": "bf8afc8ffd4bfd3d9c373e417f041d9f1e5b863f", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "psr/cache": "^2.0|^3.0", "psr/log": "^1.1|^2|^3", - "symfony/cache-contracts": "^2.5|^3", + "symfony/cache-contracts": "^3.6", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/service-contracts": "^2.5|^3", - "symfony/var-exporter": "^6.3.6|^7.0" + "symfony/var-exporter": "^6.4|^7.0" }, "conflict": { - "doctrine/dbal": "<2.13.1", - "symfony/dependency-injection": "<5.4", - "symfony/http-kernel": "<5.4", - "symfony/var-dumper": "<5.4" + "doctrine/dbal": "<3.6", + "symfony/dependency-injection": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/var-dumper": "<6.4" }, "provide": { "psr/cache-implementation": "2.0|3.0", @@ -8289,15 +10366,16 @@ }, "require-dev": { "cache/integration-tests": "dev-master", - "doctrine/dbal": "^2.13.1|^3|^4", + "doctrine/dbal": "^3.6|^4", "predis/predis": "^1.1|^2.0", "psr/simple-cache": "^1.0|^2.0|^3.0", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/filesystem": "^5.4|^6.0|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/filesystem": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -8332,7 +10410,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v6.4.16" + "source": "https://github.com/symfony/cache/tree/v7.3.4" }, "funding": [ { @@ -8343,25 +10421,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-20T10:10:54+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/cache-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b" + "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", - "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/5d68a57d66910405e5c0b63d6f0af941e66fc868", + "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868", "shasum": "" }, "require": { @@ -8375,7 +10457,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -8408,7 +10490,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/cache-contracts/tree/v3.6.0" }, "funding": [ { @@ -8424,24 +10506,24 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2025-03-13T15:25:07+00:00" }, { "name": "symfony/clock", - "version": "v6.4.13", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/clock.git", - "reference": "b2bf55c4dd115003309eafa87ee7df9ed3dde81b" + "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/clock/zipball/b2bf55c4dd115003309eafa87ee7df9ed3dde81b", - "reference": "b2bf55c4dd115003309eafa87ee7df9ed3dde81b", + "url": "https://api.github.com/repos/symfony/clock/zipball/b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", + "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "psr/clock": "^1.0", "symfony/polyfill-php83": "^1.28" }, @@ -8482,7 +10564,7 @@ "time" ], "support": { - "source": "https://github.com/symfony/clock/tree/v6.4.13" + "source": "https://github.com/symfony/clock/tree/v7.3.0" }, "funding": [ { @@ -8498,38 +10580,38 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/config", - "version": "v6.4.14", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "4e55e7e4ffddd343671ea972216d4509f46c22ef" + "reference": "8a09223170046d2cfda3d2e11af01df2c641e961" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/4e55e7e4ffddd343671ea972216d4509f46c22ef", - "reference": "4e55e7e4ffddd343671ea972216d4509f46c22ef", + "url": "https://api.github.com/repos/symfony/config/zipball/8a09223170046d2cfda3d2e11af01df2c641e961", + "reference": "8a09223170046d2cfda3d2e11af01df2c641e961", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/filesystem": "^7.1", "symfony/polyfill-ctype": "~1.8" }, "conflict": { - "symfony/finder": "<5.4", + "symfony/finder": "<6.4", "symfony/service-contracts": "<2.5" }, "require-dev": { - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", - "symfony/finder": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^5.4|^6.0|^7.0" + "symfony/yaml": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -8557,7 +10639,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.14" + "source": "https://github.com/symfony/config/tree/v7.3.4" }, "funding": [ { @@ -8568,56 +10650,60 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-04T11:33:53+00:00" + "time": "2025-09-22T12:46:16+00:00" }, { "name": "symfony/console", - "version": "v6.4.17", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "799445db3f15768ecc382ac5699e6da0520a0a04" + "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/799445db3f15768ecc382ac5699e6da0520a0a04", - "reference": "799445db3f15768ecc382ac5699e6da0520a0a04", + "url": "https://api.github.com/repos/symfony/console/zipball/2b9c5fafbac0399a20a2e82429e2bd735dcfb7db", + "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^5.4|^6.0|^7.0" + "symfony/string": "^7.2" }, "conflict": { - "symfony/dependency-injection": "<5.4", - "symfony/dotenv": "<5.4", - "symfony/event-dispatcher": "<5.4", - "symfony/lock": "<5.4", - "symfony/process": "<5.4" + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", "symfony/http-foundation": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", - "symfony/lock": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/stopwatch": "^5.4|^6.0|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -8651,7 +10737,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.17" + "source": "https://github.com/symfony/console/tree/v7.3.4" }, "funding": [ { @@ -8662,29 +10748,33 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-07T12:07:30+00:00" + "time": "2025-09-22T15:31:00+00:00" }, { "name": "symfony/css-selector", - "version": "v6.4.13", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "cb23e97813c5837a041b73a6d63a9ddff0778f5e" + "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/cb23e97813c5837a041b73a6d63a9ddff0778f5e", - "reference": "cb23e97813c5837a041b73a6d63a9ddff0778f5e", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/601a5ce9aaad7bf10797e3663faefce9e26c24e2", + "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "type": "library", "autoload": { @@ -8716,7 +10806,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com/", "support": { - "source": "https://github.com/symfony/css-selector/tree/v6.4.13" + "source": "https://github.com/symfony/css-selector/tree/v7.3.0" }, "funding": [ { @@ -8732,44 +10822,43 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/dependency-injection", - "version": "v6.4.16", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "7a379d8871f6a36f01559c14e11141cc02eb8dc8" + "reference": "82119812ab0bf3425c1234d413efd1b19bb92ae4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/7a379d8871f6a36f01559c14e11141cc02eb8dc8", - "reference": "7a379d8871f6a36f01559c14e11141cc02eb8dc8", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/82119812ab0bf3425c1234d413efd1b19bb92ae4", + "reference": "82119812ab0bf3425c1234d413efd1b19bb92ae4", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "psr/container": "^1.1|^2.0", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/service-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.2.10|^7.0" + "symfony/service-contracts": "^3.5", + "symfony/var-exporter": "^6.4.20|^7.2.5" }, "conflict": { "ext-psr": "<1.1|>=2", - "symfony/config": "<6.1", - "symfony/finder": "<5.4", - "symfony/proxy-manager-bridge": "<6.3", - "symfony/yaml": "<5.4" + "symfony/config": "<6.4", + "symfony/finder": "<6.4", + "symfony/yaml": "<6.4" }, "provide": { "psr/container-implementation": "1.1|2.0", "symfony/service-implementation": "1.1|2.0|3.0" }, "require-dev": { - "symfony/config": "^6.1|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/yaml": "^5.4|^6.0|^7.0" + "symfony/config": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -8797,7 +10886,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.16" + "source": "https://github.com/symfony/dependency-injection/tree/v7.3.4" }, "funding": [ { @@ -8808,25 +10897,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-25T14:52:46+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { @@ -8839,12 +10932,121 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "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": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com/", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.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-25T14:21:43+00:00" + }, + { + "name": "symfony/doctrine-bridge", + "version": "v7.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/doctrine-bridge.git", + "reference": "21cd48c34a47a0d0e303a590a67c3450fde55888" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/21cd48c34a47a0d0e303a590a67c3450fde55888", + "reference": "21cd48c34a47a0d0e303a590a67c3450fde55888", + "shasum": "" + }, + "require": { + "doctrine/event-manager": "^2", + "doctrine/persistence": "^3.1|^4", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "doctrine/collections": "<1.8", + "doctrine/dbal": "<3.6", + "doctrine/lexer": "<1.1", + "doctrine/orm": "<2.15", + "symfony/cache": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/form": "<6.4.6|>=7,<7.0.6", + "symfony/http-foundation": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/lock": "<6.4", + "symfony/messenger": "<6.4", + "symfony/property-info": "<6.4", + "symfony/security-bundle": "<6.4", + "symfony/security-core": "<6.4", + "symfony/validator": "<6.4" + }, + "require-dev": { + "doctrine/collections": "^1.8|^2.0", + "doctrine/data-fixtures": "^1.1|^2", + "doctrine/dbal": "^3.6|^4", + "doctrine/orm": "^2.15|^3", + "psr/log": "^1|^2|^3", + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/doctrine-messenger": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/form": "^6.4.6|^7.0.6", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/type-info": "^7.1.8", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" }, + "type": "symfony-bridge", "autoload": { - "files": [ - "function.php" + "psr-4": { + "Symfony\\Bridge\\Doctrine\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -8853,18 +11055,18 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "A generic function and convention to trigger deprecation notices", + "description": "Provides integration for Doctrine with various Symfony components", "homepage": "https://symfony.com/", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/doctrine-bridge/tree/v7.3.4" }, "funding": [ { @@ -8875,81 +11077,44 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2025-09-24T09:56:23+00:00" }, { - "name": "symfony/doctrine-bridge", - "version": "v6.4.17", + "name": "symfony/dom-crawler", + "version": "v7.3.3", "source": { "type": "git", - "url": "https://github.com/symfony/doctrine-bridge.git", - "reference": "2ba7e747a944b69f9f583c35173560afebbff995" + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "efa076ea0eeff504383ff0dcf827ea5ce15690ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/2ba7e747a944b69f9f583c35173560afebbff995", - "reference": "2ba7e747a944b69f9f583c35173560afebbff995", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/efa076ea0eeff504383ff0dcf827ea5ce15690ba", + "reference": "efa076ea0eeff504383ff0dcf827ea5ce15690ba", "shasum": "" }, "require": { - "doctrine/event-manager": "^1.2|^2", - "doctrine/persistence": "^3.1", - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", + "masterminds/html5": "^2.6", + "php": ">=8.2", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^2.5|^3" - }, - "conflict": { - "doctrine/dbal": "<2.13.1", - "doctrine/lexer": "<1.1", - "doctrine/orm": "<2.15", - "symfony/cache": "<5.4", - "symfony/dependency-injection": "<6.2", - "symfony/form": "<5.4.38|>=6,<6.4.6|>=7,<7.0.6", - "symfony/http-foundation": "<6.3", - "symfony/http-kernel": "<6.2", - "symfony/lock": "<6.3", - "symfony/messenger": "<5.4", - "symfony/property-info": "<5.4", - "symfony/security-bundle": "<5.4", - "symfony/security-core": "<6.4", - "symfony/validator": "<6.4" + "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { - "doctrine/collections": "^1.0|^2.0", - "doctrine/data-fixtures": "^1.1|^2", - "doctrine/dbal": "^2.13.1|^3|^4", - "doctrine/orm": "^2.15|^3", - "psr/log": "^1|^2|^3", - "symfony/cache": "^5.4|^6.0|^7.0", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^6.2|^7.0", - "symfony/doctrine-messenger": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/form": "^5.4.38|^6.4.6|^7.0.6", - "symfony/http-kernel": "^6.3|^7.0", - "symfony/lock": "^6.3|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/property-access": "^5.4|^6.0|^7.0", - "symfony/property-info": "^5.4|^6.0|^7.0", - "symfony/proxy-manager-bridge": "^6.4", - "symfony/security-core": "^6.4|^7.0", - "symfony/stopwatch": "^5.4|^6.0|^7.0", - "symfony/translation": "^5.4|^6.0|^7.0", - "symfony/uid": "^5.4|^6.0|^7.0", - "symfony/validator": "^6.4|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "symfony/css-selector": "^6.4|^7.0" }, - "type": "symfony-bridge", + "type": "library", "autoload": { "psr-4": { - "Symfony\\Bridge\\Doctrine\\": "" + "Symfony\\Component\\DomCrawler\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -8969,10 +11134,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides integration for Doctrine with various Symfony components", + "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com/", "support": { - "source": "https://github.com/symfony/doctrine-bridge/tree/v6.4.17" + "source": "https://github.com/symfony/dom-crawler/tree/v7.3.3" }, "funding": [ { @@ -8983,37 +11148,41 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-18T10:42:42+00:00" + "time": "2025-08-06T20:13:54+00:00" }, { "name": "symfony/dotenv", - "version": "v6.4.16", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/dotenv.git", - "reference": "1ac5e7e7e862d4d574258daf08bd569ba926e4a5" + "reference": "2192790a11f9e22cbcf9dc705a3ff22a5503923a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dotenv/zipball/1ac5e7e7e862d4d574258daf08bd569ba926e4a5", - "reference": "1ac5e7e7e862d4d574258daf08bd569ba926e4a5", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/2192790a11f9e22cbcf9dc705a3ff22a5503923a", + "reference": "2192790a11f9e22cbcf9dc705a3ff22a5503923a", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "conflict": { - "symfony/console": "<5.4", - "symfony/process": "<5.4" + "symfony/console": "<6.4", + "symfony/process": "<6.4" }, "require-dev": { - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0" + "symfony/console": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -9046,7 +11215,7 @@ "environment" ], "support": { - "source": "https://github.com/symfony/dotenv/tree/v6.4.16" + "source": "https://github.com/symfony/dotenv/tree/v7.3.2" }, "funding": [ { @@ -9057,40 +11226,46 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-27T11:08:19+00:00" + "time": "2025-07-10T08:29:33+00:00" }, { "name": "symfony/error-handler", - "version": "v6.4.17", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "37ad2380e8c1a8cf62a1200a5c10080b679b446c" + "reference": "99f81bc944ab8e5dae4f21b4ca9972698bbad0e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/37ad2380e8c1a8cf62a1200a5c10080b679b446c", - "reference": "37ad2380e8c1a8cf62a1200a5c10080b679b446c", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/99f81bc944ab8e5dae4f21b4ca9972698bbad0e4", + "reference": "99f81bc944ab8e5dae4f21b4ca9972698bbad0e4", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "symfony/var-dumper": "^6.4|^7.0" }, "conflict": { "symfony/deprecation-contracts": "<2.5", "symfony/http-kernel": "<6.4" }, "require-dev": { + "symfony/console": "^6.4|^7.0", "symfony/deprecation-contracts": "^2.5|^3", "symfony/http-kernel": "^6.4|^7.0", - "symfony/serializer": "^5.4|^6.0|^7.0" + "symfony/serializer": "^6.4|^7.0", + "symfony/webpack-encore-bundle": "^1.0|^2.0" }, "bin": [ "Resources/bin/patch-type-declarations" @@ -9121,7 +11296,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.17" + "source": "https://github.com/symfony/error-handler/tree/v7.3.4" }, "funding": [ { @@ -9132,33 +11307,37 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-06T13:30:51+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.4.13", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e" + "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", - "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b7dc69e71de420ac04bc9ab830cf3ffebba48191", + "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { - "symfony/dependency-injection": "<5.4", + "symfony/dependency-injection": "<6.4", "symfony/service-contracts": "<2.5" }, "provide": { @@ -9167,13 +11346,13 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/error-handler": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^5.4|^6.0|^7.0" + "symfony/stopwatch": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -9201,7 +11380,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.13" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.3.3" }, "funding": [ { @@ -9212,25 +11391,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2025-08-13T11:49:31+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f" + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7642f5e970b672283b7823222ae8ef8bbc160b9f", - "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", "shasum": "" }, "require": { @@ -9244,7 +11427,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -9277,7 +11460,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" }, "funding": [ { @@ -9293,25 +11476,25 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/expression-language", - "version": "v6.4.13", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/expression-language.git", - "reference": "3524904fb026356a5230cd197f9a4e6a61e0e7df" + "reference": "32d2d19c62e58767e6552166c32fb259975d2b23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/expression-language/zipball/3524904fb026356a5230cd197f9a4e6a61e0e7df", - "reference": "3524904fb026356a5230cd197f9a4e6a61e0e7df", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/32d2d19c62e58767e6552166c32fb259975d2b23", + "reference": "32d2d19c62e58767e6552166c32fb259975d2b23", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/cache": "^5.4|^6.0|^7.0", + "php": ">=8.2", + "symfony/cache": "^6.4|^7.0", "symfony/deprecation-contracts": "^2.5|^3", "symfony/service-contracts": "^2.5|^3" }, @@ -9341,7 +11524,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.13" + "source": "https://github.com/symfony/expression-language/tree/v7.3.2" }, "funding": [ { @@ -9352,34 +11535,38 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-10-09T08:40:40+00:00" + "time": "2025-07-10T08:29:33+00:00" }, { "name": "symfony/filesystem", - "version": "v6.4.13", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3" + "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/4856c9cf585d5a0313d8d35afd681a526f038dd3", - "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/edcbb768a186b5c3f25d0643159a787d3e63b7fd", + "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.8" }, "require-dev": { - "symfony/process": "^5.4|^6.4|^7.0" + "symfony/process": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -9407,7 +11594,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com/", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.4.13" + "source": "https://github.com/symfony/filesystem/tree/v7.3.2" }, "funding": [ { @@ -9418,32 +11605,36 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-10-25T15:07:50+00:00" + "time": "2025-07-07T08:17:47+00:00" }, { "name": "symfony/finder", - "version": "v6.4.17", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7" + "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7", - "reference": "1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7", + "url": "https://api.github.com/repos/symfony/finder/zipball/2a6614966ba1074fa93dae0bc804227422df4dfe", + "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "symfony/filesystem": "^6.0|^7.0" + "symfony/filesystem": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -9471,7 +11662,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.17" + "source": "https://github.com/symfony/finder/tree/v7.3.2" }, "funding": [ { @@ -9482,25 +11673,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-29T13:51:37+00:00" + "time": "2025-07-15T13:41:35+00:00" }, { "name": "symfony/flex", - "version": "v2.4.7", + "version": "v2.8.2", "source": { "type": "git", "url": "https://github.com/symfony/flex.git", - "reference": "92f4fba342161ff36072bd3b8e0b3c6c23160402" + "reference": "f356aa35f3cf3d2f46c31d344c1098eb2d260426" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/flex/zipball/92f4fba342161ff36072bd3b8e0b3c6c23160402", - "reference": "92f4fba342161ff36072bd3b8e0b3c6c23160402", + "url": "https://api.github.com/repos/symfony/flex/zipball/f356aa35f3cf3d2f46c31d344c1098eb2d260426", + "reference": "f356aa35f3cf3d2f46c31d344c1098eb2d260426", "shasum": "" }, "require": { @@ -9539,7 +11734,7 @@ "description": "Composer plugin for Symfony", "support": { "issues": "https://github.com/symfony/flex/issues", - "source": "https://github.com/symfony/flex/tree/v2.4.7" + "source": "https://github.com/symfony/flex/tree/v2.8.2" }, "funding": [ { @@ -9550,65 +11745,69 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-10-07T08:51:54+00:00" + "time": "2025-08-22T07:17:23+00:00" }, { "name": "symfony/form", - "version": "v6.4.13", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/form.git", - "reference": "0fe17f90af23908ddc11dc23507db98e66572046" + "reference": "7b3eee0f4d4dfd1ff1be70a27474197330c61736" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/form/zipball/0fe17f90af23908ddc11dc23507db98e66572046", - "reference": "0fe17f90af23908ddc11dc23507db98e66572046", + "url": "https://api.github.com/repos/symfony/form/zipball/7b3eee0f4d4dfd1ff1be70a27474197330c61736", + "reference": "7b3eee0f4d4dfd1ff1be70a27474197330c61736", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", - "symfony/options-resolver": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/options-resolver": "^7.3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-icu": "^1.21", "symfony/polyfill-mbstring": "~1.0", - "symfony/property-access": "^5.4|^6.0|^7.0", + "symfony/property-access": "^6.4|^7.0", "symfony/service-contracts": "^2.5|^3" }, "conflict": { - "symfony/console": "<5.4", - "symfony/dependency-injection": "<5.4", - "symfony/doctrine-bridge": "<5.4.21|>=6,<6.2.7", - "symfony/error-handler": "<5.4", - "symfony/framework-bundle": "<5.4", - "symfony/http-kernel": "<5.4", - "symfony/translation": "<5.4.35|>=6.0,<6.3.12|>=6.4,<6.4.3|>=7.0,<7.0.3", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<6.4", + "symfony/error-handler": "<6.4", + "symfony/framework-bundle": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/translation": "<6.4.3|>=7.0,<7.0.3", "symfony/translation-contracts": "<2.5", - "symfony/twig-bridge": "<6.3" + "symfony/twig-bridge": "<6.4" }, "require-dev": { "doctrine/collections": "^1.0|^2.0", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/html-sanitizer": "^6.1|^7.0", - "symfony/http-foundation": "^5.4|^6.0|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/intl": "^5.4|^6.0|^7.0", - "symfony/security-core": "^6.2|^7.0", - "symfony/security-csrf": "^5.4|^6.0|^7.0", - "symfony/translation": "^5.4.35|~6.3.12|^6.4.3|^7.0.3", - "symfony/uid": "^5.4|^6.0|^7.0", - "symfony/validator": "^5.4|^6.0|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/html-sanitizer": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/security-csrf": "^6.4|^7.0", + "symfony/translation": "^6.4.3|^7.0.3", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -9636,7 +11835,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.13" + "source": "https://github.com/symfony/form/tree/v7.3.4" }, "funding": [ { @@ -9647,117 +11846,126 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-10-09T08:40:40+00:00" + "time": "2025-09-22T15:31:00+00:00" }, { "name": "symfony/framework-bundle", - "version": "v6.4.17", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "17d8ae2e7aa77154f942e8ac48849ac718b0963f" + "reference": "b13e7cec5a144c8dba6f4233a2c53c00bc29e140" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/17d8ae2e7aa77154f942e8ac48849ac718b0963f", - "reference": "17d8ae2e7aa77154f942e8ac48849ac718b0963f", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/b13e7cec5a144c8dba6f4233a2c53c00bc29e140", + "reference": "b13e7cec5a144c8dba6f4233a2c53c00bc29e140", "shasum": "" }, "require": { "composer-runtime-api": ">=2.1", "ext-xml": "*", - "php": ">=8.1", - "symfony/cache": "^5.4|^6.0|^7.0", - "symfony/config": "^6.1|^7.0", - "symfony/dependency-injection": "^6.4.12|^7.0", + "php": ">=8.2", + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^7.3", + "symfony/dependency-injection": "^7.2", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/error-handler": "^6.1|^7.0", - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", - "symfony/filesystem": "^5.4|^6.0|^7.0", - "symfony/finder": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4", + "symfony/error-handler": "^7.3", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/filesystem": "^7.1", + "symfony/finder": "^6.4|^7.0", + "symfony/http-foundation": "^7.3", + "symfony/http-kernel": "^7.2", "symfony/polyfill-mbstring": "~1.0", "symfony/routing": "^6.4|^7.0" }, "conflict": { - "doctrine/annotations": "<1.13.1", "doctrine/persistence": "<1.3", "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "symfony/asset": "<5.4", + "symfony/asset": "<6.4", "symfony/asset-mapper": "<6.4", - "symfony/clock": "<6.3", - "symfony/console": "<5.4|>=7.0", + "symfony/clock": "<6.4", + "symfony/console": "<6.4", "symfony/dom-crawler": "<6.4", - "symfony/dotenv": "<5.4", - "symfony/form": "<5.4", - "symfony/http-client": "<6.3", - "symfony/lock": "<5.4", - "symfony/mailer": "<5.4", - "symfony/messenger": "<6.3", + "symfony/dotenv": "<6.4", + "symfony/form": "<6.4", + "symfony/http-client": "<6.4", + "symfony/json-streamer": ">=7.4", + "symfony/lock": "<6.4", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", "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/object-mapper": ">=7.4", + "symfony/property-access": "<6.4", + "symfony/property-info": "<6.4", + "symfony/runtime": "<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", - "symfony/serializer": "<6.4", - "symfony/stopwatch": "<5.4", - "symfony/translation": "<6.4", - "symfony/twig-bridge": "<5.4", - "symfony/twig-bundle": "<5.4", + "symfony/security-core": "<6.4", + "symfony/security-csrf": "<7.2", + "symfony/serializer": "<7.2.5", + "symfony/stopwatch": "<6.4", + "symfony/translation": "<7.3", + "symfony/twig-bridge": "<6.4", + "symfony/twig-bundle": "<6.4", "symfony/validator": "<6.4", "symfony/web-profiler-bundle": "<6.4", - "symfony/workflow": "<6.4" + "symfony/webhook": "<7.2", + "symfony/workflow": "<7.3.0-beta2" }, "require-dev": { - "doctrine/annotations": "^1.13.1|^2", "doctrine/persistence": "^1.3|^2|^3", "dragonmantank/cron-expression": "^3.1", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "seld/jsonlint": "^1.10", - "symfony/asset": "^5.4|^6.0|^7.0", + "symfony/asset": "^6.4|^7.0", "symfony/asset-mapper": "^6.4|^7.0", - "symfony/browser-kit": "^5.4|^6.0|^7.0", - "symfony/clock": "^6.2|^7.0", - "symfony/console": "^5.4.9|^6.0.9|^7.0", - "symfony/css-selector": "^5.4|^6.0|^7.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", "symfony/dom-crawler": "^6.4|^7.0", - "symfony/dotenv": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/form": "^5.4|^6.0|^7.0", - "symfony/html-sanitizer": "^6.1|^7.0", - "symfony/http-client": "^6.3|^7.0", - "symfony/lock": "^5.4|^6.0|^7.0", - "symfony/mailer": "^5.4|^6.0|^7.0", - "symfony/messenger": "^6.3|^7.0", + "symfony/dotenv": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/html-sanitizer": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/json-streamer": "7.3.*", + "symfony/lock": "^6.4|^7.0", + "symfony/mailer": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", "symfony/mime": "^6.4|^7.0", - "symfony/notifier": "^5.4|^6.0|^7.0", + "symfony/notifier": "^6.4|^7.0", + "symfony/object-mapper": "^v7.3.0-beta2", "symfony/polyfill-intl-icu": "~1.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/property-info": "^5.4|^6.0|^7.0", - "symfony/rate-limiter": "^5.4|^6.0|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", "symfony/scheduler": "^6.4.4|^7.0.4", - "symfony/security-bundle": "^5.4|^6.0|^7.0", - "symfony/semaphore": "^5.4|^6.0|^7.0", - "symfony/serializer": "^6.4|^7.0", - "symfony/stopwatch": "^5.4|^6.0|^7.0", - "symfony/string": "^5.4|^6.0|^7.0", - "symfony/translation": "^6.4|^7.0", - "symfony/twig-bundle": "^5.4|^6.0|^7.0", - "symfony/uid": "^5.4|^6.0|^7.0", + "symfony/security-bundle": "^6.4|^7.0", + "symfony/semaphore": "^6.4|^7.0", + "symfony/serializer": "^7.2.5", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/string": "^6.4|^7.0", + "symfony/translation": "^7.3", + "symfony/twig-bundle": "^6.4|^7.0", + "symfony/type-info": "^7.1.8", + "symfony/uid": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0", - "symfony/web-link": "^5.4|^6.0|^7.0", - "symfony/workflow": "^6.4|^7.0", - "symfony/yaml": "^5.4|^6.0|^7.0", - "twig/twig": "^2.10|^3.0.4" + "symfony/web-link": "^6.4|^7.0", + "symfony/webhook": "^7.2", + "symfony/workflow": "^7.3", + "symfony/yaml": "^6.4|^7.0", + "twig/twig": "^3.12" }, "type": "symfony-bundle", "autoload": { @@ -9785,7 +11993,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.17" + "source": "https://github.com/symfony/framework-bundle/tree/v7.3.4" }, "funding": [ { @@ -9796,37 +12004,44 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-19T14:08:41+00:00" + "time": "2025-09-17T05:51:54+00:00" }, { "name": "symfony/http-client", - "version": "v6.4.17", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "88898d842eb29d7e1a903724c94e90a6ca9c0509" + "reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/88898d842eb29d7e1a903724c94e90a6ca9c0509", - "reference": "88898d842eb29d7e1a903724c94e90a6ca9c0509", + "url": "https://api.github.com/repos/symfony/http-client/zipball/4b62871a01c49457cf2a8e560af7ee8a94b87a62", + "reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", "symfony/http-client-contracts": "~3.4.4|^3.5.2", + "symfony/polyfill-php83": "^1.29", "symfony/service-contracts": "^2.5|^3" }, "conflict": { + "amphp/amp": "<2.5", + "amphp/socket": "<1.1", "php-http/discovery": "<1.15", - "symfony/http-foundation": "<6.3" + "symfony/http-foundation": "<6.4" }, "provide": { "php-http/async-client-implementation": "*", @@ -9835,19 +12050,19 @@ "symfony/http-client-implementation": "3.0" }, "require-dev": { - "amphp/amp": "^2.5", - "amphp/http-client": "^4.2.1", - "amphp/http-tunnel": "^1.0", - "amphp/socket": "^1.1", + "amphp/http-client": "^4.2.1|^5.0", + "amphp/http-tunnel": "^1.0|^2.0", "guzzlehttp/promises": "^1.4|^2.0", "nyholm/psr7": "^1.0", "php-http/httplug": "^1.0|^2.0", "psr/http-client": "^1.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/stopwatch": "^5.4|^6.0|^7.0" + "symfony/amphp-http-client-meta": "^1.0|^2.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -9878,7 +12093,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.4.17" + "source": "https://github.com/symfony/http-client/tree/v7.3.4" }, "funding": [ { @@ -9889,25 +12104,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-18T12:18:31+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v3.5.2", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645" + "reference": "75d7043853a42837e68111812f4d964b01e5101c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ee8d807ab20fcb51267fdace50fbe3494c31e645", - "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/75d7043853a42837e68111812f4d964b01e5101c", + "reference": "75d7043853a42837e68111812f4d964b01e5101c", "shasum": "" }, "require": { @@ -9920,7 +12139,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -9956,7 +12175,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.2" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.6.0" }, "funding": [ { @@ -9972,40 +12191,42 @@ "type": "tidelift" } ], - "time": "2024-12-07T08:49:48+00:00" + "time": "2025-04-29T11:18:49+00:00" }, { "name": "symfony/http-foundation", - "version": "v6.4.16", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "431771b7a6f662f1575b3cfc8fd7617aa9864d57" + "reference": "c061c7c18918b1b64268771aad04b40be41dd2e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/431771b7a6f662f1575b3cfc8fd7617aa9864d57", - "reference": "431771b7a6f662f1575b3cfc8fd7617aa9864d57", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/c061c7c18918b1b64268771aad04b40be41dd2e6", + "reference": "c061c7c18918b1b64268771aad04b40be41dd2e6", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/polyfill-mbstring": "~1.1", "symfony/polyfill-php83": "^1.27" }, "conflict": { + "doctrine/dbal": "<3.6", "symfony/cache": "<6.4.12|>=7.0,<7.1.5" }, "require-dev": { - "doctrine/dbal": "^2.13.1|^3|^4", + "doctrine/dbal": "^3.6|^4", "predis/predis": "^1.1|^2.0", "symfony/cache": "^6.4.12|^7.1.5", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4|^7.0", - "symfony/mime": "^5.4|^6.0|^7.0", - "symfony/rate-limiter": "^5.4|^6.0|^7.0" + "symfony/clock": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -10033,7 +12254,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.16" + "source": "https://github.com/symfony/http-foundation/tree/v7.3.4" }, "funding": [ { @@ -10044,82 +12265,86 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-13T18:58:10+00:00" + "time": "2025-09-16T08:38:17+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.4.17", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "c5647393c5ce11833d13e4b70fff4b571d4ac710" + "reference": "b796dffea7821f035047235e076b60ca2446e3cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/c5647393c5ce11833d13e4b70fff4b571d4ac710", - "reference": "c5647393c5ce11833d13e4b70fff4b571d4ac710", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b796dffea7821f035047235e076b60ca2446e3cf", + "reference": "b796dffea7821f035047235e076b60ca2446e3cf", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", "symfony/error-handler": "^6.4|^7.0", - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^6.4|^7.0", + "symfony/event-dispatcher": "^7.3", + "symfony/http-foundation": "^7.3", "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/browser-kit": "<5.4", - "symfony/cache": "<5.4", - "symfony/config": "<6.1", - "symfony/console": "<5.4", + "symfony/browser-kit": "<6.4", + "symfony/cache": "<6.4", + "symfony/config": "<6.4", + "symfony/console": "<6.4", "symfony/dependency-injection": "<6.4", - "symfony/doctrine-bridge": "<5.4", - "symfony/form": "<5.4", - "symfony/http-client": "<5.4", + "symfony/doctrine-bridge": "<6.4", + "symfony/form": "<6.4", + "symfony/http-client": "<6.4", "symfony/http-client-contracts": "<2.5", - "symfony/mailer": "<5.4", - "symfony/messenger": "<5.4", - "symfony/translation": "<5.4", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/translation": "<6.4", "symfony/translation-contracts": "<2.5", - "symfony/twig-bridge": "<5.4", + "symfony/twig-bridge": "<6.4", "symfony/validator": "<6.4", - "symfony/var-dumper": "<6.3", - "twig/twig": "<2.13" + "symfony/var-dumper": "<6.4", + "twig/twig": "<3.12" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", - "symfony/browser-kit": "^5.4|^6.0|^7.0", - "symfony/clock": "^6.2|^7.0", - "symfony/config": "^6.1|^7.0", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/css-selector": "^5.4|^6.0|^7.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", "symfony/dependency-injection": "^6.4|^7.0", - "symfony/dom-crawler": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", "symfony/http-client-contracts": "^2.5|^3", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/property-access": "^5.4.5|^6.0.5|^7.0", - "symfony/routing": "^5.4|^6.0|^7.0", - "symfony/serializer": "^6.4.4|^7.0.4", - "symfony/stopwatch": "^5.4|^6.0|^7.0", - "symfony/translation": "^5.4|^6.0|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^7.1", + "symfony/routing": "^6.4|^7.0", + "symfony/serializer": "^7.1", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3", - "symfony/uid": "^5.4|^6.0|^7.0", + "symfony/uid": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0", - "symfony/var-dumper": "^5.4|^6.4|^7.0", - "symfony/var-exporter": "^6.2|^7.0", - "twig/twig": "^2.13|^3.0.4" + "symfony/var-dumper": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0", + "twig/twig": "^3.12" }, "type": "library", "autoload": { @@ -10147,7 +12372,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.17" + "source": "https://github.com/symfony/http-kernel/tree/v7.3.4" }, "funding": [ { @@ -10158,34 +12383,41 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-31T14:49:31+00:00" + "time": "2025-09-27T12:32:17+00:00" }, { "name": "symfony/intl", - "version": "v6.4.15", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/intl.git", - "reference": "b1d5e8d82615b60f229216edfee0b59e2ef66da6" + "reference": "e6db84864655885d9dac676a9d7dde0d904fda54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/intl/zipball/b1d5e8d82615b60f229216edfee0b59e2ef66da6", - "reference": "b1d5e8d82615b60f229216edfee0b59e2ef66da6", + "url": "https://api.github.com/repos/symfony/intl/zipball/e6db84864655885d9dac676a9d7dde0d904fda54", + "reference": "e6db84864655885d9dac676a9d7dde0d904fda54", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/string": "<7.1" }, "require-dev": { - "symfony/filesystem": "^5.4|^6.0|^7.0", - "symfony/finder": "^5.4|^6.0|^7.0", - "symfony/var-exporter": "^5.4|^6.0|^7.0" + "symfony/filesystem": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -10230,7 +12462,7 @@ "localization" ], "support": { - "source": "https://github.com/symfony/intl/tree/v6.4.15" + "source": "https://github.com/symfony/intl/tree/v7.3.4" }, "funding": [ { @@ -10241,48 +12473,52 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-08T15:28:48+00:00" + "time": "2025-09-08T14:11:30+00:00" }, { "name": "symfony/mailer", - "version": "v6.4.13", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "c2f7e0d8d7ac8fe25faccf5d8cac462805db2663" + "reference": "ab97ef2f7acf0216955f5845484235113047a31d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/c2f7e0d8d7ac8fe25faccf5d8cac462805db2663", - "reference": "c2f7e0d8d7ac8fe25faccf5d8cac462805db2663", + "url": "https://api.github.com/repos/symfony/mailer/zipball/ab97ef2f7acf0216955f5845484235113047a31d", + "reference": "ab97ef2f7acf0216955f5845484235113047a31d", "shasum": "" }, "require": { "egulias/email-validator": "^2.1.10|^3|^4", - "php": ">=8.1", + "php": ">=8.2", "psr/event-dispatcher": "^1", "psr/log": "^1|^2|^3", - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", - "symfony/mime": "^6.2|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/mime": "^7.2", "symfony/service-contracts": "^2.5|^3" }, "conflict": { "symfony/http-client-contracts": "<2.5", - "symfony/http-kernel": "<5.4", - "symfony/messenger": "<6.2", - "symfony/mime": "<6.2", - "symfony/twig-bridge": "<6.2.1" + "symfony/http-kernel": "<6.4", + "symfony/messenger": "<6.4", + "symfony/mime": "<6.4", + "symfony/twig-bridge": "<6.4" }, "require-dev": { - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/http-client": "^5.4|^6.0|^7.0", - "symfony/messenger": "^6.2|^7.0", - "symfony/twig-bridge": "^6.2|^7.0" + "symfony/console": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/twig-bridge": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -10310,7 +12546,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com/", "support": { - "source": "https://github.com/symfony/mailer/tree/v6.4.13" + "source": "https://github.com/symfony/mailer/tree/v7.3.4" }, "funding": [ { @@ -10321,30 +12557,33 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2025-09-17T05:51:54+00:00" }, { "name": "symfony/mime", - "version": "v6.4.17", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "ea87c8850a54ff039d3e0ab4ae5586dd4e6c0232" + "reference": "b1b828f69cbaf887fa835a091869e55df91d0e35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/ea87c8850a54ff039d3e0ab4ae5586dd4e6c0232", - "reference": "ea87c8850a54ff039d3e0ab4ae5586dd4e6c0232", + "url": "https://api.github.com/repos/symfony/mime/zipball/b1b828f69cbaf887fa835a091869e55df91d0e35", + "reference": "b1b828f69cbaf887fa835a091869e55df91d0e35", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", + "php": ">=8.2", "symfony/polyfill-intl-idn": "^1.10", "symfony/polyfill-mbstring": "^1.0" }, @@ -10352,17 +12591,17 @@ "egulias/email-validator": "~3.0.0", "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "symfony/mailer": "<5.4", + "symfony/mailer": "<6.4", "symfony/serializer": "<6.4.3|>7.0,<7.0.3" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3.1|^4", "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "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/dependency-injection": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", "symfony/serializer": "^6.4.3|^7.0.3" }, "type": "library", @@ -10395,7 +12634,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.4.17" + "source": "https://github.com/symfony/mime/tree/v7.3.4" }, "funding": [ { @@ -10406,47 +12645,50 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-02T11:09:41+00:00" + "time": "2025-09-16T08:38:17+00:00" }, { "name": "symfony/monolog-bridge", - "version": "v6.4.13", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/monolog-bridge.git", - "reference": "9d14621e59f22c2b6d030d92d37ffe5ae1e60452" + "reference": "7acf2abe23e5019451399ba69fc8ed3d61d4d8f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/9d14621e59f22c2b6d030d92d37ffe5ae1e60452", - "reference": "9d14621e59f22c2b6d030d92d37ffe5ae1e60452", + "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/7acf2abe23e5019451399ba69fc8ed3d61d4d8f0", + "reference": "7acf2abe23e5019451399ba69fc8ed3d61d4d8f0", "shasum": "" }, "require": { - "monolog/monolog": "^1.25.1|^2|^3", - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-kernel": "^5.4|^6.0|^7.0", + "monolog/monolog": "^3", + "php": ">=8.2", + "symfony/http-kernel": "^6.4|^7.0", "symfony/service-contracts": "^2.5|^3" }, "conflict": { - "symfony/console": "<5.4", - "symfony/http-foundation": "<5.4", - "symfony/security-core": "<5.4" + "symfony/console": "<6.4", + "symfony/http-foundation": "<6.4", + "symfony/security-core": "<6.4" }, "require-dev": { - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/http-client": "^5.4|^6.0|^7.0", - "symfony/mailer": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/mime": "^5.4|^6.0|^7.0", - "symfony/security-core": "^5.4|^6.0|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "symfony/console": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/mailer": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" }, "type": "symfony-bridge", "autoload": { @@ -10474,7 +12716,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.13" + "source": "https://github.com/symfony/monolog-bridge/tree/v7.3.4" }, "funding": [ { @@ -10485,12 +12727,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-10-14T08:49:08+00:00" + "time": "2025-09-24T16:45:39+00:00" }, { "name": "symfony/monolog-bundle", @@ -10575,20 +12821,20 @@ }, { "name": "symfony/options-resolver", - "version": "v6.4.16", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "368128ad168f20e22c32159b9f761e456cec0c78" + "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/368128ad168f20e22c32159b9f761e456cec0c78", - "reference": "368128ad168f20e22c32159b9f761e456cec0c78", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/0ff2f5c3df08a395232bbc3c2eb7e84912df911d", + "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3" }, "type": "library", @@ -10622,7 +12868,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v6.4.16" + "source": "https://github.com/symfony/options-resolver/tree/v7.3.3" }, "funding": [ { @@ -10633,36 +12879,40 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-20T10:57:02+00:00" + "time": "2025-08-05T10:16:07+00:00" }, { "name": "symfony/password-hasher", - "version": "v6.4.13", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/password-hasher.git", - "reference": "e97a1b31f60b8bdfc1fdedab4398538da9441d47" + "reference": "31fbe66af859582a20b803f38be96be8accdf2c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/password-hasher/zipball/e97a1b31f60b8bdfc1fdedab4398538da9441d47", - "reference": "e97a1b31f60b8bdfc1fdedab4398538da9441d47", + "url": "https://api.github.com/repos/symfony/password-hasher/zipball/31fbe66af859582a20b803f38be96be8accdf2c3", + "reference": "31fbe66af859582a20b803f38be96be8accdf2c3", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "conflict": { - "symfony/security-core": "<5.4" + "symfony/security-core": "<6.4" }, "require-dev": { - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/security-core": "^5.4|^6.0|^7.0" + "symfony/console": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -10694,7 +12944,7 @@ "password" ], "support": { - "source": "https://github.com/symfony/password-hasher/tree/v6.4.13" + "source": "https://github.com/symfony/password-hasher/tree/v7.3.0" }, "funding": [ { @@ -10710,11 +12960,11 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2025-02-04T08:22:58+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -10773,7 +13023,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { @@ -10784,6 +13034,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -10793,16 +13047,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", "shasum": "" }, "require": { @@ -10851,7 +13105,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" }, "funding": [ { @@ -10862,25 +13116,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-06-27T09:58:17+00:00" }, { "name": "symfony/polyfill-intl-icu", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-icu.git", - "reference": "d80a05e9904d2c2b9b95929f3e4b5d3a8f418d78" + "reference": "bfc8fa13dbaf21d69114b0efcd72ab700fb04d0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/d80a05e9904d2c2b9b95929f3e4b5d3a8f418d78", - "reference": "d80a05e9904d2c2b9b95929f3e4b5d3a8f418d78", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/bfc8fa13dbaf21d69114b0efcd72ab700fb04d0c", + "reference": "bfc8fa13dbaf21d69114b0efcd72ab700fb04d0c", "shasum": "" }, "require": { @@ -10935,7 +13193,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.33.0" }, "funding": [ { @@ -10946,25 +13204,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-06-20T22:24:30+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773" + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773", - "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", "shasum": "" }, "require": { @@ -11018,7 +13280,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0" }, "funding": [ { @@ -11029,16 +13291,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-09-10T14:38:51+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -11099,7 +13365,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" }, "funding": [ { @@ -11110,6 +13376,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -11119,19 +13389,20 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { + "ext-iconv": "*", "php": ">=7.2" }, "provide": { @@ -11179,7 +13450,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -11190,25 +13461,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", "shasum": "" }, "require": { @@ -11259,7 +13534,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" }, "funding": [ { @@ -11270,25 +13545,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-01-02T08:10:11+00:00" }, { - "name": "symfony/polyfill-php81", - "version": "v1.31.0", + "name": "symfony/polyfill-php82", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + "url": "https://github.com/symfony/polyfill-php82.git", + "reference": "5d2ed36f7734637dacc025f179698031951b1692" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", - "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/5d2ed36f7734637dacc025f179698031951b1692", + "reference": "5d2ed36f7734637dacc025f179698031951b1692", "shasum": "" }, "require": { @@ -11306,7 +13585,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php81\\": "" + "Symfony\\Polyfill\\Php82\\": "" }, "classmap": [ "Resources/stubs" @@ -11326,7 +13605,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.2+ features to lower PHP versions", "homepage": "https://symfony.com/", "keywords": [ "compatibility", @@ -11335,7 +13614,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php82/tree/v1.33.0" }, "funding": [ { @@ -11346,6 +13625,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -11354,17 +13637,17 @@ "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/polyfill-php82", - "version": "v1.31.0", + "name": "symfony/polyfill-php83", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php82.git", - "reference": "5d2ed36f7734637dacc025f179698031951b1692" + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/5d2ed36f7734637dacc025f179698031951b1692", - "reference": "5d2ed36f7734637dacc025f179698031951b1692", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", "shasum": "" }, "require": { @@ -11382,7 +13665,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php82\\": "" + "Symfony\\Polyfill\\Php83\\": "" }, "classmap": [ "Resources/stubs" @@ -11402,7 +13685,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.2+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", "homepage": "https://symfony.com/", "keywords": [ "compatibility", @@ -11411,7 +13694,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php82/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" }, "funding": [ { @@ -11422,25 +13705,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-07-08T02:45:35+00:00" }, { - "name": "symfony/polyfill-php83", - "version": "v1.31.0", + "name": "symfony/polyfill-php84", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", - "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", "shasum": "" }, "require": { @@ -11458,7 +13745,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php83\\": "" + "Symfony\\Polyfill\\Php84\\": "" }, "classmap": [ "Resources/stubs" @@ -11478,7 +13765,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", "homepage": "https://symfony.com/", "keywords": [ "compatibility", @@ -11487,7 +13774,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" }, "funding": [ { @@ -11498,16 +13785,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-06-24T13:30:11+00:00" }, { "name": "symfony/polyfill-uuid", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", @@ -11566,7 +13857,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.33.0" }, "funding": [ { @@ -11577,6 +13868,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -11586,20 +13881,20 @@ }, { "name": "symfony/process", - "version": "v6.4.15", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "3cb242f059c14ae08591c5c4087d1fe443564392" + "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/3cb242f059c14ae08591c5c4087d1fe443564392", - "reference": "3cb242f059c14ae08591c5c4087d1fe443564392", + "url": "https://api.github.com/repos/symfony/process/zipball/f24f8f316367b30810810d4eb30c543d7003ff3b", + "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "type": "library", "autoload": { @@ -11627,7 +13922,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com/", "support": { - "source": "https://github.com/symfony/process/tree/v6.4.15" + "source": "https://github.com/symfony/process/tree/v7.3.4" }, "funding": [ { @@ -11638,34 +13933,37 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-06T14:19:14+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/property-access", - "version": "v6.4.13", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", - "reference": "8cc779d88d12e440adaa26387bcfc25744064afe" + "reference": "4a4389e5c8bd1d0320d80a23caa6a1ac71cb81a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/8cc779d88d12e440adaa26387bcfc25744064afe", - "reference": "8cc779d88d12e440adaa26387bcfc25744064afe", + "url": "https://api.github.com/repos/symfony/property-access/zipball/4a4389e5c8bd1d0320d80a23caa6a1ac71cb81a7", + "reference": "4a4389e5c8bd1d0320d80a23caa6a1ac71cb81a7", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/property-info": "^5.4|^6.0|^7.0" + "php": ">=8.2", + "symfony/property-info": "^6.4|^7.0" }, "require-dev": { - "symfony/cache": "^5.4|^6.0|^7.0" + "symfony/cache": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -11704,7 +14002,7 @@ "reflection" ], "support": { - "source": "https://github.com/symfony/property-access/tree/v6.4.13" + "source": "https://github.com/symfony/property-access/tree/v7.3.3" }, "funding": [ { @@ -11715,44 +14013,50 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2025-08-04T15:15:28+00:00" }, { "name": "symfony/property-info", - "version": "v6.4.17", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "38b125d78e67668159f75383a293ec0c5d3f2963" + "reference": "7b6db23f23d13ada41e1cb484748a8ec028fbace" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/38b125d78e67668159f75383a293ec0c5d3f2963", - "reference": "38b125d78e67668159f75383a293ec0c5d3f2963", + "url": "https://api.github.com/repos/symfony/property-info/zipball/7b6db23f23d13ada41e1cb484748a8ec028fbace", + "reference": "7b6db23f23d13ada41e1cb484748a8ec028fbace", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/string": "^5.4|^6.0|^7.0" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/string": "^6.4|^7.0", + "symfony/type-info": "~7.2.8|^7.3.1" }, "conflict": { - "doctrine/annotations": "<1.12", "phpdocumentor/reflection-docblock": "<5.2", "phpdocumentor/type-resolver": "<1.5.1", - "symfony/dependency-injection": "<5.4|>=6.0,<6.4" + "symfony/cache": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/serializer": "<6.4" }, "require-dev": { - "doctrine/annotations": "^1.12|^2", "phpdocumentor/reflection-docblock": "^5.2", "phpstan/phpdoc-parser": "^1.0|^2.0", - "symfony/cache": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/serializer": "^5.4|^6.4|^7.0" + "symfony/cache": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -11788,7 +14092,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v6.4.17" + "source": "https://github.com/symfony/property-info/tree/v7.3.4" }, "funding": [ { @@ -11799,45 +14103,49 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-26T19:01:29+00:00" + "time": "2025-09-15T13:55:54+00:00" }, { "name": "symfony/psr-http-message-bridge", - "version": "v6.4.13", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/psr-http-message-bridge.git", - "reference": "c9cf83326a1074f83a738fc5320945abf7fb7fec" + "reference": "03f2f72319e7acaf2a9f6fcbe30ef17eec51594f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/c9cf83326a1074f83a738fc5320945abf7fb7fec", - "reference": "c9cf83326a1074f83a738fc5320945abf7fb7fec", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/03f2f72319e7acaf2a9f6fcbe30ef17eec51594f", + "reference": "03f2f72319e7acaf2a9f6fcbe30ef17eec51594f", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "psr/http-message": "^1.0|^2.0", - "symfony/http-foundation": "^5.4|^6.0|^7.0" + "symfony/http-foundation": "^6.4|^7.0" }, "conflict": { "php-http/discovery": "<1.15", - "symfony/http-kernel": "<6.2" + "symfony/http-kernel": "<6.4" }, "require-dev": { "nyholm/psr7": "^1.1", "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" + "symfony/browser-kit": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0" }, "type": "symfony-bridge", "autoload": { @@ -11871,7 +14179,7 @@ "psr-7" ], "support": { - "source": "https://github.com/symfony/psr-http-message-bridge/tree/v6.4.13" + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.3.0" }, "funding": [ { @@ -11887,30 +14195,29 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2024-09-26T08:57:56+00:00" }, { "name": "symfony/rate-limiter", - "version": "v6.4.15", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/rate-limiter.git", - "reference": "e250d82fc17b277b97cbce94efef5414aff29bf9" + "reference": "7e855541d302ba752f86fb0e97932e7969fe9c04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/rate-limiter/zipball/e250d82fc17b277b97cbce94efef5414aff29bf9", - "reference": "e250d82fc17b277b97cbce94efef5414aff29bf9", + "url": "https://api.github.com/repos/symfony/rate-limiter/zipball/7e855541d302ba752f86fb0e97932e7969fe9c04", + "reference": "7e855541d302ba752f86fb0e97932e7969fe9c04", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/options-resolver": "^5.4|^6.0|^7.0" + "php": ">=8.2", + "symfony/options-resolver": "^7.3" }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", - "symfony/lock": "^5.4|^6.0|^7.0" + "symfony/lock": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -11942,7 +14249,7 @@ "rate-limiter" ], "support": { - "source": "https://github.com/symfony/rate-limiter/tree/v6.4.15" + "source": "https://github.com/symfony/rate-limiter/tree/v7.3.2" }, "funding": [ { @@ -11953,45 +14260,47 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-09T07:19:24+00:00" + "time": "2025-07-07T08:17:57+00:00" }, { "name": "symfony/routing", - "version": "v6.4.16", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "91e02e606b4b705c2f4fb42f7e7708b7923a3220" + "reference": "8dc648e159e9bac02b703b9fbd937f19ba13d07c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/91e02e606b4b705c2f4fb42f7e7708b7923a3220", - "reference": "91e02e606b4b705c2f4fb42f7e7708b7923a3220", + "url": "https://api.github.com/repos/symfony/routing/zipball/8dc648e159e9bac02b703b9fbd937f19ba13d07c", + "reference": "8dc648e159e9bac02b703b9fbd937f19ba13d07c", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { - "doctrine/annotations": "<1.12", - "symfony/config": "<6.2", - "symfony/dependency-injection": "<5.4", - "symfony/yaml": "<5.4" + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/yaml": "<6.4" }, "require-dev": { - "doctrine/annotations": "^1.12|^2", "psr/log": "^1|^2|^3", - "symfony/config": "^6.2|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^5.4|^6.0|^7.0", - "symfony/yaml": "^5.4|^6.0|^7.0" + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -12025,7 +14334,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.4.16" + "source": "https://github.com/symfony/routing/tree/v7.3.4" }, "funding": [ { @@ -12036,40 +14345,44 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-13T15:31:34+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/runtime", - "version": "v6.4.14", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/runtime.git", - "reference": "4facd4174f45cd37c65860403412b67c7381136a" + "reference": "3550e2711e30bfa5d808514781cd52d1cc1d9e9f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/runtime/zipball/4facd4174f45cd37c65860403412b67c7381136a", - "reference": "4facd4174f45cd37c65860403412b67c7381136a", + "url": "https://api.github.com/repos/symfony/runtime/zipball/3550e2711e30bfa5d808514781cd52d1cc1d9e9f", + "reference": "3550e2711e30bfa5d808514781cd52d1cc1d9e9f", "shasum": "" }, "require": { "composer-plugin-api": "^1.0|^2.0", - "php": ">=8.1" + "php": ">=8.2" }, "conflict": { - "symfony/dotenv": "<5.4" + "symfony/dotenv": "<6.4" }, "require-dev": { - "composer/composer": "^1.0.2|^2.0", - "symfony/console": "^5.4.9|^6.0.9|^7.0", - "symfony/dotenv": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^5.4|^6.0|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0" + "composer/composer": "^2.6", + "symfony/console": "^6.4|^7.0", + "symfony/dotenv": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0" }, "type": "composer-plugin", "extra": { @@ -12104,7 +14417,7 @@ "runtime" ], "support": { - "source": "https://github.com/symfony/runtime/tree/v6.4.14" + "source": "https://github.com/symfony/runtime/tree/v7.3.4" }, "funding": [ { @@ -12115,80 +14428,78 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-05T16:39:55+00:00" + "time": "2025-09-11T15:31:28+00:00" }, { "name": "symfony/security-bundle", - "version": "v6.4.13", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/security-bundle.git", - "reference": "181d1fcf5f88ef8212ed7f6434e5ff51c9d7dff3" + "reference": "f750d9abccbeaa433c56f6a4eb2073166476a75a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-bundle/zipball/181d1fcf5f88ef8212ed7f6434e5ff51c9d7dff3", - "reference": "181d1fcf5f88ef8212ed7f6434e5ff51c9d7dff3", + "url": "https://api.github.com/repos/symfony/security-bundle/zipball/f750d9abccbeaa433c56f6a4eb2073166476a75a", + "reference": "f750d9abccbeaa433c56f6a4eb2073166476a75a", "shasum": "" }, "require": { "composer-runtime-api": ">=2.1", "ext-xml": "*", - "php": ">=8.1", - "symfony/clock": "^6.3|^7.0", - "symfony/config": "^6.1|^7.0", + "php": ">=8.2", + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^7.3", "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", - "symfony/http-kernel": "^6.2", - "symfony/password-hasher": "^5.4|^6.0|^7.0", - "symfony/security-core": "^6.2|^7.0", - "symfony/security-csrf": "^5.4|^6.0|^7.0", - "symfony/security-http": "^6.3.6|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/password-hasher": "^6.4|^7.0", + "symfony/security-core": "^7.3", + "symfony/security-csrf": "^6.4|^7.0", + "symfony/security-http": "^7.3", "symfony/service-contracts": "^2.5|^3" }, "conflict": { - "symfony/browser-kit": "<5.4", - "symfony/console": "<5.4", + "symfony/browser-kit": "<6.4", + "symfony/console": "<6.4", "symfony/framework-bundle": "<6.4", - "symfony/http-client": "<5.4", - "symfony/ldap": "<5.4", + "symfony/http-client": "<6.4", + "symfony/ldap": "<6.4", "symfony/serializer": "<6.4", - "symfony/twig-bundle": "<5.4", + "symfony/twig-bundle": "<6.4", "symfony/validator": "<6.4" }, "require-dev": { - "symfony/asset": "^5.4|^6.0|^7.0", - "symfony/browser-kit": "^5.4|^6.0|^7.0", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/css-selector": "^5.4|^6.0|^7.0", - "symfony/dom-crawler": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/form": "^5.4|^6.0|^7.0", + "symfony/asset": "^6.4|^7.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", "symfony/framework-bundle": "^6.4|^7.0", - "symfony/http-client": "^5.4|^6.0|^7.0", - "symfony/ldap": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/rate-limiter": "^5.4|^6.0|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/ldap": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", "symfony/serializer": "^6.4|^7.0", - "symfony/translation": "^5.4|^6.0|^7.0", - "symfony/twig-bridge": "^5.4|^6.0|^7.0", - "symfony/twig-bundle": "^5.4|^6.0|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/twig-bridge": "^6.4|^7.0", + "symfony/twig-bundle": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0", - "symfony/yaml": "^5.4|^6.0|^7.0", - "twig/twig": "^2.13|^3.0.4", - "web-token/jwt-checker": "^3.1", - "web-token/jwt-signature-algorithm-ecdsa": "^3.1", - "web-token/jwt-signature-algorithm-eddsa": "^3.1", - "web-token/jwt-signature-algorithm-hmac": "^3.1", - "web-token/jwt-signature-algorithm-none": "^3.1", - "web-token/jwt-signature-algorithm-rsa": "^3.1" + "symfony/yaml": "^6.4|^7.0", + "twig/twig": "^3.12", + "web-token/jwt-library": "^3.3.2|^4.0" }, "type": "symfony-bundle", "autoload": { @@ -12216,7 +14527,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.13" + "source": "https://github.com/symfony/security-bundle/tree/v7.3.4" }, "funding": [ { @@ -12227,53 +14538,58 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2025-09-22T15:31:00+00:00" }, { "name": "symfony/security-core", - "version": "v6.4.16", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/security-core.git", - "reference": "19cdb7de86e556202ab16e0cffd1a97348231bc0" + "reference": "68b9d3ca57615afde6152a1e1441fa035bea43f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-core/zipball/19cdb7de86e556202ab16e0cffd1a97348231bc0", - "reference": "19cdb7de86e556202ab16e0cffd1a97348231bc0", + "url": "https://api.github.com/repos/symfony/security-core/zipball/68b9d3ca57615afde6152a1e1441fa035bea43f8", + "reference": "68b9d3ca57615afde6152a1e1441fa035bea43f8", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/event-dispatcher-contracts": "^2.5|^3", - "symfony/password-hasher": "^5.4|^6.0|^7.0", + "symfony/password-hasher": "^6.4|^7.0", "symfony/service-contracts": "^2.5|^3" }, "conflict": { - "symfony/event-dispatcher": "<5.4", - "symfony/http-foundation": "<5.4", - "symfony/ldap": "<5.4", - "symfony/security-guard": "<5.4", - "symfony/translation": "<5.4.35|>=6.0,<6.3.12|>=6.4,<6.4.3|>=7.0,<7.0.3", - "symfony/validator": "<5.4" + "symfony/dependency-injection": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/http-foundation": "<6.4", + "symfony/ldap": "<6.4", + "symfony/translation": "<6.4.3|>=7.0,<7.0.3", + "symfony/validator": "<6.4" }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", "psr/container": "^1.1|^2.0", "psr/log": "^1|^2|^3", - "symfony/cache": "^5.4|^6.0|^7.0", - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^5.4|^6.0|^7.0", - "symfony/ldap": "^5.4|^6.0|^7.0", - "symfony/string": "^5.4|^6.0|^7.0", - "symfony/translation": "^5.4.35|~6.3.12|^6.4.3|^7.0.3", + "symfony/cache": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/ldap": "^6.4|^7.0", + "symfony/string": "^6.4|^7.0", + "symfony/translation": "^6.4.3|^7.0.3", "symfony/validator": "^6.4|^7.0" }, "type": "library", @@ -12302,7 +14618,7 @@ "description": "Symfony Security Component - Core Library", "homepage": "https://symfony.com/", "support": { - "source": "https://github.com/symfony/security-core/tree/v6.4.16" + "source": "https://github.com/symfony/security-core/tree/v7.3.4" }, "funding": [ { @@ -12313,36 +14629,42 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-27T09:48:51+00:00" + "time": "2025-09-24T14:32:13+00:00" }, { "name": "symfony/security-csrf", - "version": "v6.4.13", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/security-csrf.git", - "reference": "c34421b7d34efbaef5d611ab2e646a0ec464ffe3" + "reference": "2b4b0c46c901729e4e90719eacd980381f53e0a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-csrf/zipball/c34421b7d34efbaef5d611ab2e646a0ec464ffe3", - "reference": "c34421b7d34efbaef5d611ab2e646a0ec464ffe3", + "url": "https://api.github.com/repos/symfony/security-csrf/zipball/2b4b0c46c901729e4e90719eacd980381f53e0a3", + "reference": "2b4b0c46c901729e4e90719eacd980381f53e0a3", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/security-core": "^5.4|^6.0|^7.0" + "php": ">=8.2", + "symfony/security-core": "^6.4|^7.0" }, "conflict": { - "symfony/http-foundation": "<5.4" + "symfony/http-foundation": "<6.4" }, "require-dev": { - "symfony/http-foundation": "^5.4|^6.0|^7.0" + "psr/log": "^1|^2|^3", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -12370,7 +14692,7 @@ "description": "Symfony Security Component - CSRF Library", "homepage": "https://symfony.com/", "support": { - "source": "https://github.com/symfony/security-csrf/tree/v6.4.13" + "source": "https://github.com/symfony/security-csrf/tree/v7.3.0" }, "funding": [ { @@ -12386,51 +14708,51 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2025-01-02T18:42:10+00:00" }, { "name": "symfony/security-http", - "version": "v6.4.15", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/security-http.git", - "reference": "ded1e078f952e686b058d9eac98e497bea47b308" + "reference": "1cf54d0648ebab23bf9b8972617b79f1995e13a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-http/zipball/ded1e078f952e686b058d9eac98e497bea47b308", - "reference": "ded1e078f952e686b058d9eac98e497bea47b308", + "url": "https://api.github.com/repos/symfony/security-http/zipball/1cf54d0648ebab23bf9b8972617b79f1995e13a9", + "reference": "1cf54d0648ebab23bf9b8972617b79f1995e13a9", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-foundation": "^6.2|^7.0", - "symfony/http-kernel": "^6.3|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", "symfony/polyfill-mbstring": "~1.0", - "symfony/property-access": "^5.4|^6.0|^7.0", - "symfony/security-core": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/security-core": "^7.3", "symfony/service-contracts": "^2.5|^3" }, "conflict": { - "symfony/clock": "<6.3", - "symfony/event-dispatcher": "<5.4.9|>=6,<6.0.9", + "symfony/clock": "<6.4", + "symfony/event-dispatcher": "<6.4", "symfony/http-client-contracts": "<3.0", - "symfony/security-bundle": "<5.4", - "symfony/security-csrf": "<5.4" + "symfony/security-bundle": "<6.4", + "symfony/security-csrf": "<6.4" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/cache": "^5.4|^6.0|^7.0", - "symfony/clock": "^6.3|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/cache": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", "symfony/http-client-contracts": "^3.0", - "symfony/rate-limiter": "^5.4|^6.0|^7.0", - "symfony/routing": "^5.4|^6.0|^7.0", - "symfony/security-csrf": "^5.4|^6.0|^7.0", - "symfony/translation": "^5.4|^6.0|^7.0", - "web-token/jwt-checker": "^3.1", - "web-token/jwt-signature-algorithm-ecdsa": "^3.1" + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/security-csrf": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "web-token/jwt-library": "^3.3.2|^4.0" }, "type": "library", "autoload": { @@ -12458,7 +14780,7 @@ "description": "Symfony Security Component - HTTP Integration", "homepage": "https://symfony.com/", "support": { - "source": "https://github.com/symfony/security-http/tree/v6.4.15" + "source": "https://github.com/symfony/security-http/tree/v7.3.4" }, "funding": [ { @@ -12469,66 +14791,71 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-13T13:40:18+00:00" + "time": "2025-09-09T17:06:44+00:00" }, { "name": "symfony/serializer", - "version": "v6.4.15", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "9d862d66198f3c2e30404228629ef4c18d5d608e" + "reference": "0df5af266c6fe9a855af7db4fea86e13b9ca3ab1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/9d862d66198f3c2e30404228629ef4c18d5d608e", - "reference": "9d862d66198f3c2e30404228629ef4c18d5d608e", + "url": "https://api.github.com/repos/symfony/serializer/zipball/0df5af266c6fe9a855af7db4fea86e13b9ca3ab1", + "reference": "0df5af266c6fe9a855af7db4fea86e13b9ca3ab1", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-ctype": "~1.8" + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php84": "^1.30" }, "conflict": { - "doctrine/annotations": "<1.12", "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "symfony/dependency-injection": "<5.4", - "symfony/property-access": "<5.4", - "symfony/property-info": "<5.4.24|>=6,<6.2.11", - "symfony/uid": "<5.4", + "symfony/dependency-injection": "<6.4", + "symfony/property-access": "<6.4", + "symfony/property-info": "<6.4", + "symfony/uid": "<6.4", "symfony/validator": "<6.4", - "symfony/yaml": "<5.4" + "symfony/yaml": "<6.4" }, "require-dev": { - "doctrine/annotations": "^1.12|^2", "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", + "phpstan/phpdoc-parser": "^1.0|^2.0", "seld/jsonlint": "^1.10", - "symfony/cache": "^5.4|^6.0|^7.0", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/error-handler": "^5.4|^6.0|^7.0", - "symfony/filesystem": "^5.4|^6.0|^7.0", - "symfony/form": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^5.4|^6.0|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/mime": "^5.4|^6.0|^7.0", - "symfony/property-access": "^5.4.26|^6.3|^7.0", - "symfony/property-info": "^5.4.24|^6.2.11|^7.0", + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^7.2", + "symfony/error-handler": "^6.4|^7.0", + "symfony/filesystem": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3", - "symfony/uid": "^5.4|^6.0|^7.0", + "symfony/type-info": "^7.1.8", + "symfony/uid": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0", - "symfony/var-exporter": "^5.4|^6.0|^7.0", - "symfony/yaml": "^5.4|^6.0|^7.0" + "symfony/var-dumper": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -12556,7 +14883,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.15" + "source": "https://github.com/symfony/serializer/tree/v7.3.4" }, "funding": [ { @@ -12567,25 +14894,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-10-23T13:25:59+00:00" + "time": "2025-09-15T13:39:02+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", "shasum": "" }, "require": { @@ -12603,7 +14934,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -12639,7 +14970,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" }, "funding": [ { @@ -12655,36 +14986,36 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2025-04-25T09:37:31+00:00" }, { "name": "symfony/stimulus-bundle", - "version": "v2.22.1", + "version": "v2.30.0", "source": { "type": "git", "url": "https://github.com/symfony/stimulus-bundle.git", - "reference": "e13034d428354023c82a1db108d40fdf6cec2d36" + "reference": "668b9efe9d0ab8b4e50091263171609e0459c0c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stimulus-bundle/zipball/e13034d428354023c82a1db108d40fdf6cec2d36", - "reference": "e13034d428354023c82a1db108d40fdf6cec2d36", + "url": "https://api.github.com/repos/symfony/stimulus-bundle/zipball/668b9efe9d0ab8b4e50091263171609e0459c0c8", + "reference": "668b9efe9d0ab8b4e50091263171609e0459c0c8", "shasum": "" }, "require": { "php": ">=8.1", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/config": "^5.4|^6.0|^7.0|^8.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0|^8.0", "symfony/deprecation-contracts": "^2.0|^3.0", - "symfony/finder": "^5.4|^6.0|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0|^8.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0|^8.0", "twig/twig": "^2.15.3|^3.8" }, "require-dev": { - "symfony/asset-mapper": "^6.3|^7.0", - "symfony/framework-bundle": "^5.4|^6.0|^7.0", - "symfony/phpunit-bridge": "^5.4|^6.0|^7.0", - "symfony/twig-bundle": "^5.4|^6.0|^7.0", + "symfony/asset-mapper": "^6.3|^7.0|^8.0", + "symfony/framework-bundle": "^5.4|^6.0|^7.0|^8.0", + "symfony/phpunit-bridge": "^5.4|^6.0|^7.0|^8.0", + "symfony/twig-bundle": "^5.4|^6.0|^7.0|^8.0", "zenstruck/browser": "^1.4" }, "type": "symfony-bundle", @@ -12708,7 +15039,7 @@ "symfony-ux" ], "support": { - "source": "https://github.com/symfony/stimulus-bundle/tree/v2.22.1" + "source": "https://github.com/symfony/stimulus-bundle/tree/v2.30.0" }, "funding": [ { @@ -12719,29 +15050,33 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-06T14:30:33+00:00" + "time": "2025-08-27T15:25:48+00:00" }, { "name": "symfony/stopwatch", - "version": "v6.4.13", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "2cae0a6f8d04937d02f6d19806251e2104d54f92" + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/2cae0a6f8d04937d02f6d19806251e2104d54f92", - "reference": "2cae0a6f8d04937d02f6d19806251e2104d54f92", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/service-contracts": "^2.5|^3" }, "type": "library", @@ -12770,7 +15105,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com/", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v6.4.13" + "source": "https://github.com/symfony/stopwatch/tree/v7.3.0" }, "funding": [ { @@ -12786,24 +15121,24 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2025-02-24T10:49:57+00:00" }, { "name": "symfony/string", - "version": "v6.4.15", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "73a5e66ea2e1677c98d4449177c5a9cf9d8b4c6f" + "reference": "f96476035142921000338bad71e5247fbc138872" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/73a5e66ea2e1677c98d4449177c5a9cf9d8b4c6f", - "reference": "73a5e66ea2e1677c98d4449177c5a9cf9d8b4c6f", + "url": "https://api.github.com/repos/symfony/string/zipball/f96476035142921000338bad71e5247fbc138872", + "reference": "f96476035142921000338bad71e5247fbc138872", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", @@ -12813,11 +15148,11 @@ "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/error-handler": "^5.4|^6.0|^7.0", - "symfony/http-client": "^5.4|^6.0|^7.0", - "symfony/intl": "^6.2|^7.0", + "symfony/emoji": "^7.1", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^5.4|^6.0|^7.0" + "symfony/var-exporter": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -12856,7 +15191,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.15" + "source": "https://github.com/symfony/string/tree/v7.3.4" }, "funding": [ { @@ -12867,60 +15202,65 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-13T13:31:12+00:00" + "time": "2025-09-11T14:36:48+00:00" }, { "name": "symfony/translation", - "version": "v6.4.13", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "bee9bfabfa8b4045a66bf82520e492cddbaffa66" + "reference": "ec25870502d0c7072d086e8ffba1420c85965174" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/bee9bfabfa8b4045a66bf82520e492cddbaffa66", - "reference": "bee9bfabfa8b4045a66bf82520e492cddbaffa66", + "url": "https://api.github.com/repos/symfony/translation/zipball/ec25870502d0c7072d086e8ffba1420c85965174", + "reference": "ec25870502d0c7072d086e8ffba1420c85965174", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/translation-contracts": "^2.5|^3.0" }, "conflict": { - "symfony/config": "<5.4", - "symfony/console": "<5.4", - "symfony/dependency-injection": "<5.4", + "nikic/php-parser": "<5.0", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", "symfony/http-client-contracts": "<2.5", - "symfony/http-kernel": "<5.4", + "symfony/http-kernel": "<6.4", "symfony/service-contracts": "<2.5", - "symfony/twig-bundle": "<5.4", - "symfony/yaml": "<5.4" + "symfony/twig-bundle": "<6.4", + "symfony/yaml": "<6.4" }, "provide": { "symfony/translation-implementation": "2.3|3.0" }, "require-dev": { - "nikic/php-parser": "^4.18|^5.0", + "nikic/php-parser": "^5.0", "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", "symfony/http-client-contracts": "^2.5|^3.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/intl": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", "symfony/polyfill-intl-icu": "^1.21", - "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/routing": "^6.4|^7.0", "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^5.4|^6.0|^7.0" + "symfony/yaml": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -12951,7 +15291,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com/", "support": { - "source": "https://github.com/symfony/translation/tree/v6.4.13" + "source": "https://github.com/symfony/translation/tree/v7.3.4" }, "funding": [ { @@ -12962,25 +15302,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-27T18:14:25+00:00" + "time": "2025-09-07T11:39:36+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "4667ff3bd513750603a09c8dedbea942487fb07c" + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/4667ff3bd513750603a09c8dedbea942487fb07c", - "reference": "4667ff3bd513750603a09c8dedbea942487fb07c", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d", "shasum": "" }, "require": { @@ -12993,7 +15337,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -13029,7 +15373,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0" }, "funding": [ { @@ -13045,72 +15389,74 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-27T08:32:26+00:00" }, { "name": "symfony/twig-bridge", - "version": "v6.4.17", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "238e1aac992b5231c66faf10131ace7bdba97065" + "reference": "33558f013b7f6ed72805527c8405cae0062e47c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/238e1aac992b5231c66faf10131ace7bdba97065", - "reference": "238e1aac992b5231c66faf10131ace7bdba97065", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/33558f013b7f6ed72805527c8405cae0062e47c5", + "reference": "33558f013b7f6ed72805527c8405cae0062e47c5", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/translation-contracts": "^2.5|^3", - "twig/twig": "^2.13|^3.0.4" + "twig/twig": "^3.21" }, "conflict": { "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "symfony/console": "<5.4", - "symfony/form": "<6.3", - "symfony/http-foundation": "<5.4", + "symfony/console": "<6.4", + "symfony/form": "<6.4", + "symfony/http-foundation": "<6.4", "symfony/http-kernel": "<6.4", - "symfony/mime": "<6.2", + "symfony/mime": "<6.4", "symfony/serializer": "<6.4", - "symfony/translation": "<5.4", - "symfony/workflow": "<5.4" + "symfony/translation": "<6.4", + "symfony/workflow": "<6.4" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3|^4", "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/asset": "^5.4|^6.0|^7.0", - "symfony/asset-mapper": "^6.3|^7.0", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/finder": "^5.4|^6.0|^7.0", - "symfony/form": "^6.4|^7.0", - "symfony/html-sanitizer": "^6.1|^7.0", - "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/asset": "^6.4|^7.0", + "symfony/asset-mapper": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/emoji": "^7.1", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/form": "^6.4.20|^7.2.5", + "symfony/html-sanitizer": "^6.4|^7.0", + "symfony/http-foundation": "^7.3", "symfony/http-kernel": "^6.4|^7.0", - "symfony/intl": "^5.4|^6.0|^7.0", - "symfony/mime": "^6.2|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", "symfony/polyfill-intl-icu": "~1.0", - "symfony/property-info": "^5.4|^6.0|^7.0", - "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", "symfony/security-acl": "^2.8|^3.0", - "symfony/security-core": "^5.4|^6.0|^7.0", - "symfony/security-csrf": "^5.4|^6.0|^7.0", - "symfony/security-http": "^5.4|^6.0|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/security-csrf": "^6.4|^7.0", + "symfony/security-http": "^6.4|^7.0", "symfony/serializer": "^6.4.3|^7.0.3", - "symfony/stopwatch": "^5.4|^6.0|^7.0", - "symfony/translation": "^6.1|^7.0", - "symfony/web-link": "^5.4|^6.0|^7.0", - "symfony/workflow": "^5.4|^6.0|^7.0", - "symfony/yaml": "^5.4|^6.0|^7.0", - "twig/cssinliner-extra": "^2.12|^3", - "twig/inky-extra": "^2.12|^3", - "twig/markdown-extra": "^2.12|^3" + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0", + "symfony/workflow": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0", + "twig/cssinliner-extra": "^3", + "twig/inky-extra": "^3", + "twig/markdown-extra": "^3" }, "type": "symfony-bridge", "autoload": { @@ -13138,7 +15484,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.17" + "source": "https://github.com/symfony/twig-bridge/tree/v7.3.3" }, "funding": [ { @@ -13149,52 +15495,56 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-19T14:08:41+00:00" + "time": "2025-08-18T13:10:53+00:00" }, { "name": "symfony/twig-bundle", - "version": "v6.4.13", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "c3beeb5336aba1ea03c37e526968c2fde3ef25c4" + "reference": "da5c778a8416fcce5318737c4d944f6fa2bb3f81" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/c3beeb5336aba1ea03c37e526968c2fde3ef25c4", - "reference": "c3beeb5336aba1ea03c37e526968c2fde3ef25c4", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/da5c778a8416fcce5318737c4d944f6fa2bb3f81", + "reference": "da5c778a8416fcce5318737c4d944f6fa2bb3f81", "shasum": "" }, "require": { "composer-runtime-api": ">=2.1", - "php": ">=8.1", - "symfony/config": "^6.1|^7.0", - "symfony/dependency-injection": "^6.1|^7.0", - "symfony/http-foundation": "^5.4|^6.0|^7.0", - "symfony/http-kernel": "^6.2", - "symfony/twig-bridge": "^6.4", - "twig/twig": "^2.13|^3.0.4" + "php": ">=8.2", + "symfony/config": "^7.3", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/twig-bridge": "^7.3", + "twig/twig": "^3.12" }, "conflict": { - "symfony/framework-bundle": "<5.4", - "symfony/translation": "<5.4" + "symfony/framework-bundle": "<6.4", + "symfony/translation": "<6.4" }, "require-dev": { - "symfony/asset": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/finder": "^5.4|^6.0|^7.0", - "symfony/form": "^5.4|^6.0|^7.0", - "symfony/framework-bundle": "^5.4|^6.0|^7.0", - "symfony/routing": "^5.4|^6.0|^7.0", - "symfony/stopwatch": "^5.4|^6.0|^7.0", - "symfony/translation": "^5.4|^6.0|^7.0", - "symfony/web-link": "^5.4|^6.0|^7.0", - "symfony/yaml": "^5.4|^6.0|^7.0" + "symfony/asset": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" }, "type": "symfony-bundle", "autoload": { @@ -13222,7 +15572,90 @@ "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.13" + "source": "https://github.com/symfony/twig-bundle/tree/v7.3.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-09-10T12:00:31+00:00" + }, + { + "name": "symfony/type-info", + "version": "v7.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/type-info.git", + "reference": "d34eaeb57f39c8a9c97eb72a977c423207dfa35b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/type-info/zipball/d34eaeb57f39c8a9c97eb72a977c423207dfa35b", + "reference": "d34eaeb57f39c8a9c97eb72a977c423207dfa35b", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.30" + }, + "require-dev": { + "phpstan/phpdoc-parser": "^1.30|^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\TypeInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mathias Arlaud", + "email": "mathias.arlaud@gmail.com" + }, + { + "name": "Baptiste LEDUC", + "email": "baptiste.leduc@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts PHP types information.", + "homepage": "https://symfony.com/", + "keywords": [ + "PHPStan", + "phpdoc", + "symfony", + "type" + ], + "support": { + "source": "https://github.com/symfony/type-info/tree/v7.3.4" }, "funding": [ { @@ -13233,33 +15666,37 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2025-09-11T15:33:27+00:00" }, { "name": "symfony/uid", - "version": "v6.4.13", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "18eb207f0436a993fffbdd811b5b8fa35fa5e007" + "reference": "a69f69f3159b852651a6bf45a9fdd149520525bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/18eb207f0436a993fffbdd811b5b8fa35fa5e007", - "reference": "18eb207f0436a993fffbdd811b5b8fa35fa5e007", + "url": "https://api.github.com/repos/symfony/uid/zipball/a69f69f3159b852651a6bf45a9fdd149520525bb", + "reference": "a69f69f3159b852651a6bf45a9fdd149520525bb", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/polyfill-uuid": "^1.15" }, "require-dev": { - "symfony/console": "^5.4|^6.0|^7.0" + "symfony/console": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -13296,7 +15733,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v6.4.13" + "source": "https://github.com/symfony/uid/tree/v7.3.1" }, "funding": [ { @@ -13312,33 +15749,34 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2025-06-27T19:55:54+00:00" }, { "name": "symfony/ux-translator", - "version": "v2.22.1", + "version": "v2.30.0", "source": { "type": "git", "url": "https://github.com/symfony/ux-translator.git", - "reference": "f05e243a52258431b69c85ef9de6c0ed0d9c92c1" + "reference": "9616091db206df4caa7d8dce2e48941512b1a94a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ux-translator/zipball/f05e243a52258431b69c85ef9de6c0ed0d9c92c1", - "reference": "f05e243a52258431b69c85ef9de6c0ed0d9c92c1", + "url": "https://api.github.com/repos/symfony/ux-translator/zipball/9616091db206df4caa7d8dce2e48941512b1a94a", + "reference": "9616091db206df4caa7d8dce2e48941512b1a94a", "shasum": "" }, "require": { "php": ">=8.1", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/filesystem": "^5.4|^6.0|^7.0", - "symfony/string": "^5.4|^6.0|^7.0", - "symfony/translation": "^5.4|^6.0|^7.0" + "symfony/console": "^5.4|^6.0|^7.0|^8.0", + "symfony/filesystem": "^5.4|^6.0|^7.0|^8.0", + "symfony/string": "^5.4|^6.0|^7.0|^8.0", + "symfony/translation": "^5.4|^6.0|^7.0|^8.0" }, "require-dev": { - "symfony/framework-bundle": "^5.4|^6.0|^7.0", - "symfony/phpunit-bridge": "^5.2|^6.0|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "symfony/framework-bundle": "^5.4|^6.0|^7.0|^8.0", + "symfony/phpunit-bridge": "^5.2|^6.0|^7.0|^8.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0|^8.0", + "symfony/yaml": "^5.4|^6.0|^7.0|^8.0" }, "type": "symfony-bundle", "extra": { @@ -13372,7 +15810,7 @@ "symfony-ux" ], "support": { - "source": "https://github.com/symfony/ux-translator/tree/v2.22.1" + "source": "https://github.com/symfony/ux-translator/tree/v2.30.0" }, "funding": [ { @@ -13383,25 +15821,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-05T14:25:02+00:00" + "time": "2025-08-27T15:25:48+00:00" }, { "name": "symfony/ux-turbo", - "version": "v2.22.1", + "version": "v2.30.0", "source": { "type": "git", "url": "https://github.com/symfony/ux-turbo.git", - "reference": "97718ea4bca26f0db843c3c0de338d6900c5a002" + "reference": "c5e88c7e16713e84a2a35f36276ccdb05c2c78d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ux-turbo/zipball/97718ea4bca26f0db843c3c0de338d6900c5a002", - "reference": "97718ea4bca26f0db843c3c0de338d6900c5a002", + "url": "https://api.github.com/repos/symfony/ux-turbo/zipball/c5e88c7e16713e84a2a35f36276ccdb05c2c78d8", + "reference": "c5e88c7e16713e84a2a35f36276ccdb05c2c78d8", "shasum": "" }, "require": { @@ -13415,23 +15857,24 @@ "dbrekelmans/bdi": "dev-main", "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": "^6.4|^7.0", + "php-webdriver/webdriver": "^1.15", + "phpstan/phpstan": "^2.1.17", + "symfony/asset-mapper": "^6.4|^7.0|^8.0", + "symfony/debug-bundle": "^5.4|^6.0|^7.0|^8.0", + "symfony/expression-language": "^5.4|^6.0|^7.0|^8.0", + "symfony/form": "^5.4|^6.0|^7.0|^8.0", + "symfony/framework-bundle": "^6.4|^7.0|^8.0", "symfony/mercure-bundle": "^0.3.7", - "symfony/messenger": "^5.4|^6.0|^7.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": "^6.4|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0|^8.0", + "symfony/panther": "^2.2", + "symfony/phpunit-bridge": "^5.4|^6.0|^7.0|^8.0", + "symfony/process": "^5.4|6.3.*|^7.0|^8.0", + "symfony/property-access": "^5.4|^6.0|^7.0|^8.0", + "symfony/security-core": "^5.4|^6.0|^7.0|^8.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0|^8.0", + "symfony/twig-bundle": "^6.4|^7.0|^8.0", "symfony/ux-twig-component": "^2.21", - "symfony/web-profiler-bundle": "^5.4|^6.0|^7.0" + "symfony/web-profiler-bundle": "^5.4|^6.0|^7.0|^8.0" }, "type": "symfony-bundle", "extra": { @@ -13470,7 +15913,7 @@ "turbo-stream" ], "support": { - "source": "https://github.com/symfony/ux-turbo/tree/v2.22.1" + "source": "https://github.com/symfony/ux-turbo/tree/v2.30.0" }, "funding": [ { @@ -13481,29 +15924,33 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-05T14:25:02+00:00" + "time": "2025-08-27T15:25:48+00:00" }, { "name": "symfony/validator", - "version": "v6.4.17", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "a3c19a0e542d427c207e22242043ef35b5b99a2c" + "reference": "5e29a348b5fac2227b6938a54db006d673bb813a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/a3c19a0e542d427c207e22242043ef35b5b99a2c", - "reference": "a3c19a0e542d427c207e22242043ef35b5b99a2c", + "url": "https://api.github.com/repos/symfony/validator/zipball/5e29a348b5fac2227b6938a54db006d673bb813a", + "reference": "5e29a348b5fac2227b6938a54db006d673bb813a", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0", @@ -13511,34 +15958,35 @@ "symfony/translation-contracts": "^2.5|^3" }, "conflict": { - "doctrine/annotations": "<1.13", "doctrine/lexer": "<1.1", - "symfony/dependency-injection": "<5.4", - "symfony/expression-language": "<5.4", - "symfony/http-kernel": "<5.4", - "symfony/intl": "<5.4", - "symfony/property-info": "<5.4", - "symfony/translation": "<5.4.35|>=6.0,<6.3.12|>=6.4,<6.4.3|>=7.0,<7.0.3", - "symfony/yaml": "<5.4" + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<7.0", + "symfony/expression-language": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/intl": "<6.4", + "symfony/property-info": "<6.4", + "symfony/translation": "<6.4.3|>=7.0,<7.0.3", + "symfony/yaml": "<6.4" }, "require-dev": { - "doctrine/annotations": "^1.13|^2", "egulias/email-validator": "^2.1.10|^3|^4", - "symfony/cache": "^5.4|^6.0|^7.0", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/finder": "^5.4|^6.0|^7.0", - "symfony/http-client": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^5.4|^6.0|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/intl": "^5.4|^6.0|^7.0", - "symfony/mime": "^5.4|^6.0|^7.0", - "symfony/property-access": "^5.4|^6.0|^7.0", - "symfony/property-info": "^5.4|^6.0|^7.0", - "symfony/translation": "^5.4.35|~6.3.12|^6.4.3|^7.0.3", - "symfony/yaml": "^5.4|^6.0|^7.0" + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/string": "^6.4|^7.0", + "symfony/translation": "^6.4.3|^7.0.3", + "symfony/type-info": "^7.1.8", + "symfony/yaml": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -13567,7 +16015,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com/", "support": { - "source": "https://github.com/symfony/validator/tree/v6.4.17" + "source": "https://github.com/symfony/validator/tree/v7.3.4" }, "funding": [ { @@ -13578,43 +16026,45 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-29T12:50:19+00:00" + "time": "2025-09-24T06:32:27+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.4.15", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "38254d5a5ac2e61f2b52f9caf54e7aa3c9d36b80" + "reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38254d5a5ac2e61f2b52f9caf54e7aa3c9d36b80", - "reference": "38254d5a5ac2e61f2b52f9caf54e7aa3c9d36b80", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb", + "reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/console": "<5.4" + "symfony/console": "<6.4" }, "require-dev": { - "ext-iconv": "*", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/error-handler": "^6.3|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/uid": "^5.4|^6.0|^7.0", - "twig/twig": "^2.13|^3.0.4" + "symfony/console": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/uid": "^6.4|^7.0", + "twig/twig": "^3.12" }, "bin": [ "Resources/bin/var-dump-server" @@ -13652,7 +16102,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.4.15" + "source": "https://github.com/symfony/var-dumper/tree/v7.3.4" }, "funding": [ { @@ -13663,35 +16113,39 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-08T15:28:48+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/var-exporter", - "version": "v6.4.13", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "0f605f72a363f8743001038a176eeb2a11223b51" + "reference": "0f020b544a30a7fe8ba972e53ee48a74c0bc87f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/0f605f72a363f8743001038a176eeb2a11223b51", - "reference": "0f605f72a363f8743001038a176eeb2a11223b51", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/0f020b544a30a7fe8ba972e53ee48a74c0bc87f4", + "reference": "0f020b544a30a7fe8ba972e53ee48a74c0bc87f4", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { "symfony/property-access": "^6.4|^7.0", "symfony/serializer": "^6.4|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "symfony/var-dumper": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -13729,7 +16183,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.4.13" + "source": "https://github.com/symfony/var-exporter/tree/v7.3.4" }, "funding": [ { @@ -13740,39 +16194,43 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/web-link", - "version": "v6.4.13", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/web-link.git", - "reference": "4d188b64bb9a9c5e2e4d20c8d5fdce6bbbb32c94" + "reference": "7697f74fce67555665339423ce453cc8216a98ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-link/zipball/4d188b64bb9a9c5e2e4d20c8d5fdce6bbbb32c94", - "reference": "4d188b64bb9a9c5e2e4d20c8d5fdce6bbbb32c94", + "url": "https://api.github.com/repos/symfony/web-link/zipball/7697f74fce67555665339423ce453cc8216a98ff", + "reference": "7697f74fce67555665339423ce453cc8216a98ff", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "psr/link": "^1.1|^2.0" }, "conflict": { - "symfony/http-kernel": "<5.4" + "symfony/http-kernel": "<6.4" }, "provide": { "psr/link-implementation": "1.0|2.0" }, "require-dev": { - "symfony/http-kernel": "^5.4|^6.0|^7.0" + "symfony/http-kernel": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -13812,7 +16270,7 @@ "push" ], "support": { - "source": "https://github.com/symfony/web-link/tree/v6.4.13" + "source": "https://github.com/symfony/web-link/tree/v7.3.0" }, "funding": [ { @@ -13828,36 +16286,36 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2025-05-19T13:28:18+00:00" }, { "name": "symfony/webpack-encore-bundle", - "version": "v2.2.0", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/symfony/webpack-encore-bundle.git", - "reference": "e335394b68a775a9b2bd173a8ba4fd2001f3870c" + "reference": "7ae70d44c24c3b913f308af8396169b5c6d9e0f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/webpack-encore-bundle/zipball/e335394b68a775a9b2bd173a8ba4fd2001f3870c", - "reference": "e335394b68a775a9b2bd173a8ba4fd2001f3870c", + "url": "https://api.github.com/repos/symfony/webpack-encore-bundle/zipball/7ae70d44c24c3b913f308af8396169b5c6d9e0f5", + "reference": "7ae70d44c24c3b913f308af8396169b5c6d9e0f5", "shasum": "" }, "require": { "php": ">=8.1.0", - "symfony/asset": "^5.4 || ^6.2 || ^7.0", - "symfony/config": "^5.4 || ^6.2 || ^7.0", - "symfony/dependency-injection": "^5.4 || ^6.2 || ^7.0", - "symfony/http-kernel": "^5.4 || ^6.2 || ^7.0", + "symfony/asset": "^5.4 || ^6.2 || ^7.0 || ^8.0", + "symfony/config": "^5.4 || ^6.2 || ^7.0 || ^8.0", + "symfony/dependency-injection": "^5.4 || ^6.2 || ^7.0 || ^8.0", + "symfony/http-kernel": "^5.4 || ^6.2 || ^7.0 || ^8.0", "symfony/service-contracts": "^1.1.9 || ^2.1.3 || ^3.0" }, "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" + "symfony/framework-bundle": "^5.4 || ^6.2 || ^7.0 || ^8.0", + "symfony/http-client": "^5.4 || ^6.2 || ^7.0 || ^8.0", + "symfony/phpunit-bridge": "^5.4 || ^6.2 || ^7.0 || ^8.0", + "symfony/twig-bundle": "^5.4 || ^6.2 || ^7.0 || ^8.0", + "symfony/web-link": "^5.4 || ^6.2 || ^7.0 || ^8.0" }, "type": "symfony-bundle", "extra": { @@ -13884,7 +16342,7 @@ "description": "Integration of your Symfony app with Webpack Encore", "support": { "issues": "https://github.com/symfony/webpack-encore-bundle/issues", - "source": "https://github.com/symfony/webpack-encore-bundle/tree/v2.2.0" + "source": "https://github.com/symfony/webpack-encore-bundle/tree/v2.3.0" }, "funding": [ { @@ -13895,37 +16353,41 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-10-02T07:27:19+00:00" + "time": "2025-08-05T11:43:32+00:00" }, { "name": "symfony/yaml", - "version": "v6.4.13", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "e99b4e94d124b29ee4cf3140e1b537d2dad8cec9" + "reference": "d4f4a66866fe2451f61296924767280ab5732d9d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/e99b4e94d124b29ee4cf3140e1b537d2dad8cec9", - "reference": "e99b4e94d124b29ee4cf3140e1b537d2dad8cec9", + "url": "https://api.github.com/repos/symfony/yaml/zipball/d4f4a66866fe2451f61296924767280ab5732d9d", + "reference": "d4f4a66866fe2451f61296924767280ab5732d9d", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/console": "<5.4" + "symfony/console": "<6.4" }, "require-dev": { - "symfony/console": "^5.4|^6.0|^7.0" + "symfony/console": "^6.4|^7.0" }, "bin": [ "Resources/bin/yaml-lint" @@ -13956,7 +16418,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com/", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.4.13" + "source": "https://github.com/symfony/yaml/tree/v7.3.3" }, "funding": [ { @@ -13967,25 +16429,90 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2025-08-27T11:34:33+00:00" + }, + { + "name": "symplify/easy-coding-standard", + "version": "12.6.0", + "source": { + "type": "git", + "url": "https://github.com/easy-coding-standard/easy-coding-standard.git", + "reference": "781e6124dc7e14768ae999a8f5309566bbe62004" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/781e6124dc7e14768ae999a8f5309566bbe62004", + "reference": "781e6124dc7e14768ae999a8f5309566bbe62004", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "conflict": { + "friendsofphp/php-cs-fixer": "<3.46", + "phpcsstandards/php_codesniffer": "<3.8", + "symplify/coding-standard": "<12.1" + }, + "suggest": { + "ext-dom": "Needed to support checkstyle output format in class CheckstyleOutputFormatter" + }, + "bin": [ + "bin/ecs" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Use Coding Standard with 0-knowledge of PHP-CS-Fixer and PHP_CodeSniffer", + "keywords": [ + "Code style", + "automation", + "fixer", + "static analysis" + ], + "support": { + "issues": "https://github.com/easy-coding-standard/easy-coding-standard/issues", + "source": "https://github.com/easy-coding-standard/easy-coding-standard/tree/12.6.0" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2025-09-10T14:21:58+00:00" }, { "name": "tecnickcom/tc-lib-barcode", - "version": "2.4.2", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/tecnickcom/tc-lib-barcode.git", - "reference": "cd5d8029eeaf6225b9ff4692364c4c473191e487" + "reference": "f238ffd120d98a34df6573590e7ed02f766a91c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tecnickcom/tc-lib-barcode/zipball/cd5d8029eeaf6225b9ff4692364c4c473191e487", - "reference": "cd5d8029eeaf6225b9ff4692364c4c473191e487", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-barcode/zipball/f238ffd120d98a34df6573590e7ed02f766a91c4", + "reference": "f238ffd120d98a34df6573590e7ed02f766a91c4", "shasum": "" }, "require": { @@ -13999,8 +16526,8 @@ "require-dev": { "pdepend/pdepend": "2.16.2", "phpmd/phpmd": "2.15.0", - "phpunit/phpunit": "11.5.2 || 10.5.40", - "squizlabs/php_codesniffer": "3.11.2" + "phpunit/phpunit": "12.2.0 || 11.5.7 || 10.5.40", + "squizlabs/php_codesniffer": "3.13.0" }, "type": "library", "autoload": { @@ -14064,28 +16591,28 @@ ], "support": { "issues": "https://github.com/tecnickcom/tc-lib-barcode/issues", - "source": "https://github.com/tecnickcom/tc-lib-barcode/tree/2.4.2" + "source": "https://github.com/tecnickcom/tc-lib-barcode/tree/2.4.8" }, "funding": [ { - "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_donations¤cy_code=GBP&business=paypal@tecnick.com&item_name=donation%20for%20tc-lib-barcode%20project", + "url": "https://www.paypal.com/donate/?hosted_button_id=NZUEC5XS8MFBJ", "type": "custom" } ], - "time": "2024-12-25T10:47:45+00:00" + "time": "2025-06-06T11:35:02+00:00" }, { "name": "tecnickcom/tc-lib-color", - "version": "2.2.7", + "version": "2.2.13", "source": { "type": "git", "url": "https://github.com/tecnickcom/tc-lib-color.git", - "reference": "5fe3c1771ab577572b3304d11496745aff45db8e" + "reference": "85d1366fb33813aa521d30e3d7c7d7d82a8103a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tecnickcom/tc-lib-color/zipball/5fe3c1771ab577572b3304d11496745aff45db8e", - "reference": "5fe3c1771ab577572b3304d11496745aff45db8e", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-color/zipball/85d1366fb33813aa521d30e3d7c7d7d82a8103a6", + "reference": "85d1366fb33813aa521d30e3d7c7d7d82a8103a6", "shasum": "" }, "require": { @@ -14095,8 +16622,8 @@ "require-dev": { "pdepend/pdepend": "2.16.2", "phpmd/phpmd": "2.15.0", - "phpunit/phpunit": "11.5.2 || 10.5.40", - "squizlabs/php_codesniffer": "3.11.2" + "phpunit/phpunit": "12.2.0 || 11.5.7 || 10.5.40", + "squizlabs/php_codesniffer": "3.13.0" }, "type": "library", "autoload": { @@ -14133,15 +16660,15 @@ ], "support": { "issues": "https://github.com/tecnickcom/tc-lib-color/issues", - "source": "https://github.com/tecnickcom/tc-lib-color/tree/2.2.7" + "source": "https://github.com/tecnickcom/tc-lib-color/tree/2.2.13" }, "funding": [ { - "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_donations¤cy_code=GBP&business=paypal@tecnick.com&item_name=donation%20for%20tc-lib-color%20project", + "url": "https://www.paypal.com/donate/?hosted_button_id=NZUEC5XS8MFBJ", "type": "custom" } ], - "time": "2024-12-24T16:32:19+00:00" + "time": "2025-06-06T11:33:19+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -14200,20 +16727,20 @@ }, { "name": "twig/cssinliner-extra", - "version": "v3.18.0", + "version": "v3.21.0", "source": { "type": "git", "url": "https://github.com/twigphp/cssinliner-extra.git", - "reference": "cef36c444b1cce4c0978d7aebd20427671a918f4" + "reference": "378d29b61d6406c456e3a4afbd15bbeea0b72ea8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/cssinliner-extra/zipball/cef36c444b1cce4c0978d7aebd20427671a918f4", - "reference": "cef36c444b1cce4c0978d7aebd20427671a918f4", + "url": "https://api.github.com/repos/twigphp/cssinliner-extra/zipball/378d29b61d6406c456e3a4afbd15bbeea0b72ea8", + "reference": "378d29b61d6406c456e3a4afbd15bbeea0b72ea8", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1.0", "symfony/deprecation-contracts": "^2.5|^3", "tijsverkoyen/css-to-inline-styles": "^2.0", "twig/twig": "^3.13|^4.0" @@ -14253,7 +16780,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/cssinliner-extra/tree/v3.18.0" + "source": "https://github.com/twigphp/cssinliner-extra/tree/v3.21.0" }, "funding": [ { @@ -14265,24 +16792,24 @@ "type": "tidelift" } ], - "time": "2024-09-03T13:08:40+00:00" + "time": "2025-01-31T20:45:36+00:00" }, { "name": "twig/extra-bundle", - "version": "v3.18.0", + "version": "v3.21.0", "source": { "type": "git", "url": "https://github.com/twigphp/twig-extra-bundle.git", - "reference": "9746573ca4bc1cd03a767a183faadaf84e0c31fa" + "reference": "62d1cf47a1aa009cbd07b21045b97d3d5cb79896" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/9746573ca4bc1cd03a767a183faadaf84e0c31fa", - "reference": "9746573ca4bc1cd03a767a183faadaf84e0c31fa", + "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/62d1cf47a1aa009cbd07b21045b97d3d5cb79896", + "reference": "62d1cf47a1aa009cbd07b21045b97d3d5cb79896", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1.0", "symfony/framework-bundle": "^5.4|^6.4|^7.0", "symfony/twig-bundle": "^5.4|^6.4|^7.0", "twig/twig": "^3.2|^4.0" @@ -14327,7 +16854,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.18.0" + "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.21.0" }, "funding": [ { @@ -14339,24 +16866,24 @@ "type": "tidelift" } ], - "time": "2024-09-26T19:22:23+00:00" + "time": "2025-02-19T14:29:33+00:00" }, { "name": "twig/html-extra", - "version": "v3.18.0", + "version": "v3.21.0", "source": { "type": "git", "url": "https://github.com/twigphp/html-extra.git", - "reference": "c63b28e192c1b7c15bb60f81d2e48b140846239a" + "reference": "5442dd707601c83b8cd4233e37bb10ab8489a90f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/html-extra/zipball/c63b28e192c1b7c15bb60f81d2e48b140846239a", - "reference": "c63b28e192c1b7c15bb60f81d2e48b140846239a", + "url": "https://api.github.com/repos/twigphp/html-extra/zipball/5442dd707601c83b8cd4233e37bb10ab8489a90f", + "reference": "5442dd707601c83b8cd4233e37bb10ab8489a90f", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1.0", "symfony/deprecation-contracts": "^2.5|^3", "symfony/mime": "^5.4|^6.4|^7.0", "twig/twig": "^3.13|^4.0" @@ -14395,7 +16922,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/html-extra/tree/v3.18.0" + "source": "https://github.com/twigphp/html-extra/tree/v3.21.0" }, "funding": [ { @@ -14407,25 +16934,25 @@ "type": "tidelift" } ], - "time": "2024-12-29T10:29:59+00:00" + "time": "2025-02-19T14:29:33+00:00" }, { "name": "twig/inky-extra", - "version": "v3.18.0", + "version": "v3.21.0", "source": { "type": "git", "url": "https://github.com/twigphp/inky-extra.git", - "reference": "60c92c2a435ccd95d7a852229f01098aaf7fbced" + "reference": "aacd79d94534b4a7fd6533cb5c33c4ee97239a0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/inky-extra/zipball/60c92c2a435ccd95d7a852229f01098aaf7fbced", - "reference": "60c92c2a435ccd95d7a852229f01098aaf7fbced", + "url": "https://api.github.com/repos/twigphp/inky-extra/zipball/aacd79d94534b4a7fd6533cb5c33c4ee97239a0d", + "reference": "aacd79d94534b4a7fd6533cb5c33c4ee97239a0d", "shasum": "" }, "require": { "lorenzo/pinky": "^1.0.5", - "php": ">=8.0.2", + "php": ">=8.1.0", "symfony/deprecation-contracts": "^2.5|^3", "twig/twig": "^3.13|^4.0" }, @@ -14465,7 +16992,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/inky-extra/tree/v3.18.0" + "source": "https://github.com/twigphp/inky-extra/tree/v3.21.0" }, "funding": [ { @@ -14477,24 +17004,24 @@ "type": "tidelift" } ], - "time": "2024-09-03T13:08:40+00:00" + "time": "2025-01-31T20:45:36+00:00" }, { "name": "twig/intl-extra", - "version": "v3.18.0", + "version": "v3.21.0", "source": { "type": "git", "url": "https://github.com/twigphp/intl-extra.git", - "reference": "4eeab2a3f8d04d1838be7251ab2d183f817aea7b" + "reference": "05bc5d46b9df9e62399eae53e7c0b0633298b146" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/intl-extra/zipball/4eeab2a3f8d04d1838be7251ab2d183f817aea7b", - "reference": "4eeab2a3f8d04d1838be7251ab2d183f817aea7b", + "url": "https://api.github.com/repos/twigphp/intl-extra/zipball/05bc5d46b9df9e62399eae53e7c0b0633298b146", + "reference": "05bc5d46b9df9e62399eae53e7c0b0633298b146", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1.0", "symfony/intl": "^5.4|^6.4|^7.0", "twig/twig": "^3.13|^4.0" }, @@ -14529,7 +17056,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/intl-extra/tree/v3.18.0" + "source": "https://github.com/twigphp/intl-extra/tree/v3.21.0" }, "funding": [ { @@ -14541,24 +17068,24 @@ "type": "tidelift" } ], - "time": "2024-11-20T13:19:52+00:00" + "time": "2025-01-31T20:45:36+00:00" }, { "name": "twig/markdown-extra", - "version": "v3.18.0", + "version": "v3.21.0", "source": { "type": "git", "url": "https://github.com/twigphp/markdown-extra.git", - "reference": "76219b06e104a706879752e6e4d79f63ef7e9f23" + "reference": "f4616e1dd375209dacf6026f846e6b537d036ce4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/76219b06e104a706879752e6e4d79f63ef7e9f23", - "reference": "76219b06e104a706879752e6e4d79f63ef7e9f23", + "url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/f4616e1dd375209dacf6026f846e6b537d036ce4", + "reference": "f4616e1dd375209dacf6026f846e6b537d036ce4", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1.0", "symfony/deprecation-contracts": "^2.5|^3", "twig/twig": "^3.13|^4.0" }, @@ -14601,7 +17128,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/markdown-extra/tree/v3.18.0" + "source": "https://github.com/twigphp/markdown-extra/tree/v3.21.0" }, "funding": [ { @@ -14613,24 +17140,24 @@ "type": "tidelift" } ], - "time": "2024-12-02T08:57:02+00:00" + "time": "2025-01-31T20:45:36+00:00" }, { "name": "twig/string-extra", - "version": "v3.18.0", + "version": "v3.21.0", "source": { "type": "git", "url": "https://github.com/twigphp/string-extra.git", - "reference": "cb4eec11de02f63ad8ea9d065a1f27752d0bf752" + "reference": "4b3337544ac8f76c280def94e32b53acfaec0589" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/string-extra/zipball/cb4eec11de02f63ad8ea9d065a1f27752d0bf752", - "reference": "cb4eec11de02f63ad8ea9d065a1f27752d0bf752", + "url": "https://api.github.com/repos/twigphp/string-extra/zipball/4b3337544ac8f76c280def94e32b53acfaec0589", + "reference": "4b3337544ac8f76c280def94e32b53acfaec0589", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1.0", "symfony/string": "^5.4|^6.4|^7.0", "symfony/translation-contracts": "^1.1|^2|^3", "twig/twig": "^3.13|^4.0" @@ -14668,7 +17195,7 @@ "unicode" ], "support": { - "source": "https://github.com/twigphp/string-extra/tree/v3.18.0" + "source": "https://github.com/twigphp/string-extra/tree/v3.21.0" }, "funding": [ { @@ -14680,28 +17207,27 @@ "type": "tidelift" } ], - "time": "2024-11-30T08:42:13+00:00" + "time": "2025-01-31T20:45:36+00:00" }, { "name": "twig/twig", - "version": "v3.18.0", + "version": "v3.21.1", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50" + "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50", - "reference": "acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/285123877d4dd97dd7c11842ac5fb7e86e60d81d", + "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1.0", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-php81": "^1.29" + "symfony/polyfill-mbstring": "^1.3" }, "require-dev": { "phpstan/phpstan": "^2.0", @@ -14748,7 +17274,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.18.0" + "source": "https://github.com/twigphp/Twig/tree/v3.21.1" }, "funding": [ { @@ -14760,20 +17286,20 @@ "type": "tidelift" } ], - "time": "2024-12-29T10:51:50+00:00" + "time": "2025-05-03T07:21:55+00:00" }, { "name": "ua-parser/uap-php", - "version": "v3.9.14", + "version": "v3.10.0", "source": { "type": "git", "url": "https://github.com/ua-parser/uap-php.git", - "reference": "b796c5ea5df588e65aeb4e2c6cce3811dec4fed6" + "reference": "f44bdd1b38198801cf60b0681d2d842980e47af5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ua-parser/uap-php/zipball/b796c5ea5df588e65aeb4e2c6cce3811dec4fed6", - "reference": "b796c5ea5df588e65aeb4e2c6cce3811dec4fed6", + "url": "https://api.github.com/repos/ua-parser/uap-php/zipball/f44bdd1b38198801cf60b0681d2d842980e47af5", + "reference": "f44bdd1b38198801cf60b0681d2d842980e47af5", "shasum": "" }, "require": { @@ -14821,43 +17347,43 @@ "description": "A multi-language port of Browserscope's user agent parser.", "support": { "issues": "https://github.com/ua-parser/uap-php/issues", - "source": "https://github.com/ua-parser/uap-php/tree/v3.9.14" + "source": "https://github.com/ua-parser/uap-php/tree/v3.10.0" }, - "time": "2020-10-02T23:36:20+00:00" + "time": "2025-07-17T15:43:24+00:00" }, { "name": "web-auth/cose-lib", - "version": "4.4.0", + "version": "4.4.2", "source": { "type": "git", "url": "https://github.com/web-auth/cose-lib.git", - "reference": "2166016e48e0214f4f63320a7758a9386d14c92a" + "reference": "a93b61c48fb587855f64a9ec11ad7b60e867cb15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/cose-lib/zipball/2166016e48e0214f4f63320a7758a9386d14c92a", - "reference": "2166016e48e0214f4f63320a7758a9386d14c92a", + "url": "https://api.github.com/repos/web-auth/cose-lib/zipball/a93b61c48fb587855f64a9ec11ad7b60e867cb15", + "reference": "a93b61c48fb587855f64a9ec11ad7b60e867cb15", "shasum": "" }, "require": { - "brick/math": "^0.9|^0.10|^0.11|^0.12", + "brick/math": "^0.9|^0.10|^0.11|^0.12|^0.13", "ext-json": "*", "ext-openssl": "*", "php": ">=8.1", "spomky-labs/pki-framework": "^1.0" }, "require-dev": { - "ekino/phpstan-banned-code": "^1.0", + "deptrac/deptrac": "^3.0", + "ekino/phpstan-banned-code": "^1.0|^2.0|^3.0", "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|^11.0", - "qossmic/deptrac": "^2.0", - "rector/rector": "^1.0", + "phpstan/phpstan": "^1.7|^2.0", + "phpstan/phpstan-deprecation-rules": "^1.0|^2.0", + "phpstan/phpstan-phpunit": "^1.1|^2.0", + "phpstan/phpstan-strict-rules": "^1.0|^2.0", + "phpunit/phpunit": "^10.1|^11.0|^12.0", + "rector/rector": "^2.0", "symfony/phpunit-bridge": "^6.4|^7.0", "symplify/easy-coding-standard": "^12.0" }, @@ -14893,7 +17419,7 @@ ], "support": { "issues": "https://github.com/web-auth/cose-lib/issues", - "source": "https://github.com/web-auth/cose-lib/tree/4.4.0" + "source": "https://github.com/web-auth/cose-lib/tree/4.4.2" }, "funding": [ { @@ -14905,48 +17431,44 @@ "type": "patreon" } ], - "time": "2024-07-18T08:47:32+00:00" + "time": "2025-08-14T20:33:29+00:00" }, { "name": "web-auth/webauthn-lib", - "version": "4.9.2", + "version": "5.2.2", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-lib.git", - "reference": "008b25171c27cf4813420d0de31cc059bcc71f1a" + "reference": "8937c397c8ae91b5af422ca8aa915c756062da74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/008b25171c27cf4813420d0de31cc059bcc71f1a", - "reference": "008b25171c27cf4813420d0de31cc059bcc71f1a", + "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/8937c397c8ae91b5af422ca8aa915c756062da74", + "reference": "8937c397c8ae91b5af422ca8aa915c756062da74", "shasum": "" }, "require": { "ext-json": "*", - "ext-mbstring": "*", "ext-openssl": "*", - "lcobucci/clock": "^2.2|^3.0", "paragonie/constant_time_encoding": "^2.6|^3.0", - "php": ">=8.1", + "php": ">=8.2", + "phpdocumentor/reflection-docblock": "^5.3", "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/clock": "^6.4|^7.0", "symfony/deprecation-contracts": "^3.2", - "symfony/uid": "^6.1|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/uid": "^6.4|^7.0", "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 fetching Metadata Statement from distant sources" }, "type": "library", @@ -14983,7 +17505,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-lib/tree/4.9.2" + "source": "https://github.com/web-auth/webauthn-lib/tree/5.2.2" }, "funding": [ { @@ -14995,41 +17517,38 @@ "type": "patreon" } ], - "time": "2025-01-04T09:47:58+00:00" + "time": "2025-03-16T14:38:43+00:00" }, { "name": "web-auth/webauthn-symfony-bundle", - "version": "4.9.2", + "version": "5.2.2", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-symfony-bundle.git", - "reference": "80aa16fa6f16ab8f017a4108ffcd2ecc12264c07" + "reference": "aebb0315b43728a92973cc3d4d471cbe414baa54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-symfony-bundle/zipball/80aa16fa6f16ab8f017a4108ffcd2ecc12264c07", - "reference": "80aa16fa6f16ab8f017a4108ffcd2ecc12264c07", + "url": "https://api.github.com/repos/web-auth/webauthn-symfony-bundle/zipball/aebb0315b43728a92973cc3d4d471cbe414baa54", + "reference": "aebb0315b43728a92973cc3d4d471cbe414baa54", "shasum": "" }, "require": { - "nyholm/psr7": "^1.5", - "php": ">=8.1", - "phpdocumentor/reflection-docblock": "^5.3", + "php": ">=8.2", "psr/event-dispatcher": "^1.0", - "symfony/config": "^6.1|^7.0", - "symfony/dependency-injection": "^6.1|^7.0", - "symfony/framework-bundle": "^6.1|^7.0", - "symfony/http-client": "^6.1|^7.0", - "symfony/property-access": "^6.1|^7.0", - "symfony/property-info": "^6.1|^7.0", - "symfony/psr-http-message-bridge": "^2.1|^6.1|^7.0", - "symfony/security-bundle": "^6.1|^7.0", - "symfony/security-core": "^6.1|^7.0", - "symfony/security-http": "^6.1|^7.0", - "symfony/serializer": "^6.1|^7.0", - "symfony/validator": "^6.1|^7.0", - "web-auth/webauthn-lib": "self.version", - "web-token/jwt-library": "^3.3|^4.0" + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/security-bundle": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/security-http": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "web-auth/webauthn-lib": "self.version" + }, + "suggest": { + "symfony/security-bundle": "Symfony firewall using a JSON API (perfect for script applications)" }, "type": "symfony-bundle", "extra": { @@ -15068,105 +17587,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-symfony-bundle/tree/4.9.2" - }, - "funding": [ - { - "url": "https://github.com/Spomky", - "type": "github" - }, - { - "url": "https://www.patreon.com/FlorentMorselli", - "type": "patreon" - } - ], - "time": "2025-01-04T09:38:56+00:00" - }, - { - "name": "web-token/jwt-library", - "version": "3.4.7", - "source": { - "type": "git", - "url": "https://github.com/web-token/jwt-library.git", - "reference": "1a25c8ced3e2b3c31d32dcfad215cbd8cb812f28" - }, - "dist": { - "type": "zip", - "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|^3.0", - "paragonie/sodium_compat": "^1.20|^2.0", - "php": ">=8.1", - "psr/cache": "^3.0", - "psr/clock": "^1.0", - "psr/http-client": "^1.0", - "psr/http-factory": "^1.0", - "spomky-labs/pki-framework": "^1.2.1", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/http-client": "^5.4|^6.0|^7.0", - "symfony/polyfill-mbstring": "^1.12" - }, - "conflict": { - "spomky-labs/jose": "*" - }, - "suggest": { - "ext-bcmath": "GMP or BCMath is highly recommended to improve the library performance", - "ext-gmp": "GMP or BCMath is highly recommended to improve the library performance", - "ext-openssl": "For key management (creation, optimization, etc.) and some algorithms (AES, RSA, ECDSA, etc.)", - "ext-sodium": "Sodium is required for OKP key creation, EdDSA signature algorithm and ECDH-ES key encryption with OKP keys", - "paragonie/sodium_compat": "Sodium is required for OKP key creation, EdDSA signature algorithm and ECDH-ES key encryption with OKP keys", - "spomky-labs/aes-key-wrap": "For all Key Wrapping algorithms (A128KW, A192KW, A256KW, A128GCMKW, A192GCMKW, A256GCMKW, PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW...)", - "symfony/http-client": "To enable JKU/X5U support." - }, - "type": "library", - "autoload": { - "psr-4": { - "Jose\\Component\\": "" - } - }, - "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-token/jwt-framework/contributors" - } - ], - "description": "JWT library", - "homepage": "https://github.com/web-token", - "keywords": [ - "JOSE", - "JWE", - "JWK", - "JWKSet", - "JWS", - "Jot", - "RFC7515", - "RFC7516", - "RFC7517", - "RFC7518", - "RFC7519", - "RFC7520", - "bundle", - "jwa", - "jwt", - "symfony" - ], - "support": { - "issues": "https://github.com/web-token/jwt-library/issues", - "source": "https://github.com/web-token/jwt-library/tree/3.4.7" + "source": "https://github.com/web-auth/webauthn-symfony-bundle/tree/5.2.2" }, "funding": [ { @@ -15178,7 +17599,7 @@ "type": "patreon" } ], - "time": "2024-07-02T16:35:11+00:00" + "time": "2025-03-24T12:00:00+00:00" }, { "name": "webmozart/assert", @@ -15298,34 +17719,36 @@ "packages-dev": [ { "name": "dama/doctrine-test-bundle", - "version": "v8.2.0", + "version": "v8.4.0", "source": { "type": "git", "url": "https://github.com/dmaicher/doctrine-test-bundle.git", - "reference": "1f81a280ea63f049d24e9c8ce00e557b18e0ff2f" + "reference": "ce7cd44126c36694e2f2d92c4aedd4fc5b0874f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dmaicher/doctrine-test-bundle/zipball/1f81a280ea63f049d24e9c8ce00e557b18e0ff2f", - "reference": "1f81a280ea63f049d24e9c8ce00e557b18e0ff2f", + "url": "https://api.github.com/repos/dmaicher/doctrine-test-bundle/zipball/ce7cd44126c36694e2f2d92c4aedd4fc5b0874f2", + "reference": "ce7cd44126c36694e2f2d92c4aedd4fc5b0874f2", "shasum": "" }, "require": { "doctrine/dbal": "^3.3 || ^4.0", - "doctrine/doctrine-bundle": "^2.11.0", - "php": "^7.4 || ^8.0", - "psr/cache": "^1.0 || ^2.0 || ^3.0", - "symfony/cache": "^5.4 || ^6.3 || ^7.0", - "symfony/framework-bundle": "^5.4 || ^6.3 || ^7.0" + "doctrine/doctrine-bundle": "^2.11.0 || ^3.0", + "php": ">= 8.1", + "psr/cache": "^2.0 || ^3.0", + "symfony/cache": "^6.4 || ^7.3 || ^8.0", + "symfony/framework-bundle": "^6.4 || ^7.3 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<10.0" }, "require-dev": { "behat/behat": "^3.0", "friendsofphp/php-cs-fixer": "^3.27", - "phpstan/phpstan": "^1.2", - "phpunit/phpunit": "^8.0 || ^9.0 || ^10.0 || ^11.0", - "symfony/phpunit-bridge": "^6.3", - "symfony/process": "^5.4 || ^6.3 || ^7.0", - "symfony/yaml": "^5.4 || ^6.3 || ^7.0" + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^10.5.57 || ^11.5.41|| ^12.3.14", + "symfony/dotenv": "^6.4 || ^7.3 || ^8.0", + "symfony/process": "^6.4 || ^7.3 || ^8.0" }, "type": "symfony-bundle", "extra": { @@ -15335,7 +17758,7 @@ }, "autoload": { "psr-4": { - "DAMA\\DoctrineTestBundle\\": "src/DAMA/DoctrineTestBundle" + "DAMA\\DoctrineTestBundle\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -15359,45 +17782,45 @@ ], "support": { "issues": "https://github.com/dmaicher/doctrine-test-bundle/issues", - "source": "https://github.com/dmaicher/doctrine-test-bundle/tree/v8.2.0" + "source": "https://github.com/dmaicher/doctrine-test-bundle/tree/v8.4.0" }, - "time": "2024-05-28T15:41:06+00:00" + "time": "2025-10-11T15:24:02+00:00" }, { "name": "doctrine/doctrine-fixtures-bundle", - "version": "4.0.0", + "version": "4.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineFixturesBundle.git", - "reference": "90185317e6bb3d845667c5ebd444d9c83ae19a01" + "reference": "cd58d7738fe1fea1dbfd3e3f3bb421ee92d45e10" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineFixturesBundle/zipball/90185317e6bb3d845667c5ebd444d9c83ae19a01", - "reference": "90185317e6bb3d845667c5ebd444d9c83ae19a01", + "url": "https://api.github.com/repos/doctrine/DoctrineFixturesBundle/zipball/cd58d7738fe1fea1dbfd3e3f3bb421ee92d45e10", + "reference": "cd58d7738fe1fea1dbfd3e3f3bb421ee92d45e10", "shasum": "" }, "require": { "doctrine/data-fixtures": "^2.0", - "doctrine/doctrine-bundle": "^2.2", + "doctrine/doctrine-bundle": "^2.2 || ^3.0", "doctrine/orm": "^2.14.0 || ^3.0", - "doctrine/persistence": "^2.4 || ^3.0", + "doctrine/persistence": "^2.4 || ^3.0 || ^4.0", "php": "^8.1", "psr/log": "^2 || ^3", - "symfony/config": "^5.4 || ^6.0 || ^7.0", - "symfony/console": "^5.4 || ^6.0 || ^7.0", - "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", + "symfony/config": "^6.4 || ^7.0", + "symfony/console": "^6.4 || ^7.0", + "symfony/dependency-injection": "^6.4 || ^7.0", "symfony/deprecation-contracts": "^2.1 || ^3", - "symfony/doctrine-bridge": "^5.4.48 || ^6.4.16 || ^7.1.9", - "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0" + "symfony/doctrine-bridge": "^6.4.16 || ^7.1.9", + "symfony/http-kernel": "^6.4 || ^7.0" }, "conflict": { "doctrine/dbal": "< 3" }, "require-dev": { - "doctrine/coding-standard": "^12", - "phpstan/phpstan": "^2", - "phpunit/phpunit": "^10.5.38 || ^11" + "doctrine/coding-standard": "14.0.0", + "phpstan/phpstan": "2.1.11", + "phpunit/phpunit": "^10.5.38 || 11.4.14" }, "type": "symfony-bundle", "autoload": { @@ -15431,7 +17854,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineFixturesBundle/issues", - "source": "https://github.com/doctrine/DoctrineFixturesBundle/tree/4.0.0" + "source": "https://github.com/doctrine/DoctrineFixturesBundle/tree/4.2.0" }, "funding": [ { @@ -15447,7 +17870,7 @@ "type": "tidelift" } ], - "time": "2024-12-05T18:35:55+00:00" + "time": "2025-10-12T16:50:54+00:00" }, { "name": "ekino/phpstan-banned-code", @@ -15517,16 +17940,16 @@ }, { "name": "jbtronics/translation-editor-bundle", - "version": "v1.0", + "version": "v1.1.2", "source": { "type": "git", "url": "https://github.com/jbtronics/translation-editor-bundle.git", - "reference": "4e771c290bf73dded061808bc3ba3c56bc35aa06" + "reference": "bab5dd6ef41e87ba3d60c6363793e1cdf5cb6249" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jbtronics/translation-editor-bundle/zipball/4e771c290bf73dded061808bc3ba3c56bc35aa06", - "reference": "4e771c290bf73dded061808bc3ba3c56bc35aa06", + "url": "https://api.github.com/repos/jbtronics/translation-editor-bundle/zipball/bab5dd6ef41e87ba3d60c6363793e1cdf5cb6249", + "reference": "bab5dd6ef41e87ba3d60c6363793e1cdf5cb6249", "shasum": "" }, "require": { @@ -15571,7 +17994,7 @@ ], "support": { "issues": "https://github.com/jbtronics/translation-editor-bundle/issues", - "source": "https://github.com/jbtronics/translation-editor-bundle/tree/v1.0" + "source": "https://github.com/jbtronics/translation-editor-bundle/tree/v1.1.2" }, "funding": [ { @@ -15583,20 +18006,20 @@ "type": "github" } ], - "time": "2024-09-03T16:44:00+00:00" + "time": "2025-07-28T09:19:13+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.12.1", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { @@ -15635,7 +18058,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { @@ -15643,20 +18066,20 @@ "type": "tidelift" } ], - "time": "2024-11-08T17:47:46+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "nikic/php-parser", - "version": "v5.4.0", + "version": "v5.6.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494" + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", "shasum": "" }, "require": { @@ -15675,7 +18098,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -15699,9 +18122,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" }, - "time": "2024-12-30T11:07:19+00:00" + "time": "2025-08-13T20:13:15+00:00" }, { "name": "phar-io/manifest", @@ -15871,16 +18294,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.1", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7" - }, + "version": "2.1.31", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7", - "reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ead89849d879fe203ce9292c6ef5e7e76f867b96", + "reference": "ead89849d879fe203ce9292c6ef5e7e76f867b96", "shasum": "" }, "require": { @@ -15925,25 +18343,25 @@ "type": "github" } ], - "time": "2025-01-05T16:43:48+00:00" + "time": "2025-10-10T14:14:11+00:00" }, { "name": "phpstan/phpstan-doctrine", - "version": "2.0.1", + "version": "2.0.10", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-doctrine.git", - "reference": "bdb6a835c5aa9725979694ae9b70591e180f4853" + "reference": "5eaf37b87288474051469aee9f937fc9d862f330" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/bdb6a835c5aa9725979694ae9b70591e180f4853", - "reference": "bdb6a835c5aa9725979694ae9b70591e180f4853", + "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/5eaf37b87288474051469aee9f937fc9d862f330", + "reference": "5eaf37b87288474051469aee9f937fc9d862f330", "shasum": "" }, "require": { "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.0.3" + "phpstan/phpstan": "^2.1.13" }, "conflict": { "doctrine/collections": "<1.0", @@ -15967,11 +18385,13 @@ "gedmo/doctrine-extensions": "^3.8", "nesbot/carbon": "^2.49", "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^2.0.2", "phpstan/phpstan-phpunit": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9.6.20", "ramsey/uuid": "^4.2", - "symfony/cache": "^5.4" + "symfony/cache": "^5.4", + "symfony/uid": "^5.4 || ^6.4 || ^7.3" }, "type": "phpstan-extension", "extra": { @@ -15994,27 +18414,27 @@ "description": "Doctrine extensions for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-doctrine/issues", - "source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.1" + "source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.10" }, - "time": "2024-12-02T16:48:00+00:00" + "time": "2025-10-06T10:01:02+00:00" }, { "name": "phpstan/phpstan-strict-rules", - "version": "2.0.1", + "version": "2.0.7", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "ed6fea0ad4ad9c7e25f3ad2e7c4d420cf1e67fe3" + "reference": "d6211c46213d4181054b3d77b10a5c5cb0d59538" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/ed6fea0ad4ad9c7e25f3ad2e7c4d420cf1e67fe3", - "reference": "ed6fea0ad4ad9c7e25f3ad2e7c4d420cf1e67fe3", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/d6211c46213d4181054b3d77b10a5c5cb0d59538", + "reference": "d6211c46213d4181054b3d77b10a5c5cb0d59538", "shasum": "" }, "require": { "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.0.4" + "phpstan/phpstan": "^2.1.29" }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", @@ -16042,28 +18462,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/2.0.1" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.7" }, - "time": "2024-12-12T20:21:10+00:00" + "time": "2025-09-26T11:19:08+00:00" }, { "name": "phpstan/phpstan-symfony", - "version": "2.0.1", + "version": "2.0.8", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "c08cd8e54a08d651bc402d304cfa161c3c3766c4" + "reference": "8820c22d785c235f69bb48da3d41e688bc8a1796" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/c08cd8e54a08d651bc402d304cfa161c3c3766c4", - "reference": "c08cd8e54a08d651bc402d304cfa161c3c3766c4", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/8820c22d785c235f69bb48da3d41e688bc8a1796", + "reference": "8820c22d785c235f69bb48da3d41e688bc8a1796", "shasum": "" }, "require": { "ext-simplexml": "*", "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.0" + "phpstan/phpstan": "^2.1.13" }, "conflict": { "symfony/framework-bundle": "<3.0" @@ -16073,7 +18493,7 @@ "phpstan/phpstan-phpunit": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9.6", - "psr/container": "1.0 || 1.1.1", + "psr/container": "1.1.2", "symfony/config": "^5.4 || ^6.1", "symfony/console": "^5.4 || ^6.1", "symfony/dependency-injection": "^5.4 || ^6.1", @@ -16113,41 +18533,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/2.0.1" + "source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.8" }, - "time": "2025-01-04T13:58:15+00:00" + "time": "2025-09-07T06:55:50+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.32", + "version": "11.0.11", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" + "reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", - "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4", + "reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.19.1 || ^5.1.0", - "php": ">=7.3", - "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", + "nikic/php-parser": "^5.4.0", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-text-template": "^4.0.1", + "sebastian/code-unit-reverse-lookup": "^4.0.1", + "sebastian/complexity": "^4.0.1", + "sebastian/environment": "^7.2.0", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.2", "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^9.6" + "phpunit/phpunit": "^11.5.2" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -16156,7 +18576,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "9.2.x-dev" + "dev-main": "11.0.x-dev" } }, "autoload": { @@ -16185,40 +18605,52 @@ "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.32" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.11" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" } ], - "time": "2024-08-22T04:23:01+00:00" + "time": "2025-08-27T14:37:49+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "3.0.6", + "version": "5.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -16245,7 +18677,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" }, "funding": [ { @@ -16253,28 +18686,28 @@ "type": "github" } ], - "time": "2021-12-02T12:48:52+00:00" + "time": "2024-08-27T05:02:59+00:00" }, { "name": "phpunit/php-invoker", - "version": "3.1.1", + "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "suggest": { "ext-pcntl": "*" @@ -16282,7 +18715,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -16308,7 +18741,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" }, "funding": [ { @@ -16316,32 +18750,32 @@ "type": "github" } ], - "time": "2020-09-28T05:58:55+00:00" + "time": "2024-07-03T05:07:44+00:00" }, { "name": "phpunit/php-text-template", - "version": "2.0.4", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -16367,7 +18801,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" }, "funding": [ { @@ -16375,32 +18810,32 @@ "type": "github" } ], - "time": "2020-10-26T05:33:50+00:00" + "time": "2024-07-03T05:08:43+00:00" }, { "name": "phpunit/php-timer", - "version": "5.0.3", + "version": "7.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -16426,7 +18861,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" }, "funding": [ { @@ -16434,54 +18870,52 @@ "type": "github" } ], - "time": "2020-10-26T13:16:10+00:00" + "time": "2024-07-03T05:09:35+00:00" }, { "name": "phpunit/phpunit", - "version": "9.6.22", + "version": "11.5.42", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c" + "reference": "1c6cb5dfe412af3d0dfd414cfd110e3b9cfdbc3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f80235cb4d3caa59ae09be3adf1ded27521d1a9c", - "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1c6cb5dfe412af3d0dfd414cfd110e3b9cfdbc3c", + "reference": "1c6cb5dfe412af3d0dfd414cfd110e3b9cfdbc3c", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.5.0 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.1", + "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", - "php": ">=7.3", - "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.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.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" + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0.11", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-invoker": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "phpunit/php-timer": "^7.0.1", + "sebastian/cli-parser": "^3.0.2", + "sebastian/code-unit": "^3.0.3", + "sebastian/comparator": "^6.3.2", + "sebastian/diff": "^6.0.2", + "sebastian/environment": "^7.2.1", + "sebastian/exporter": "^6.3.2", + "sebastian/global-state": "^7.0.2", + "sebastian/object-enumerator": "^6.0.1", + "sebastian/type": "^5.1.3", + "sebastian/version": "^5.0.2", + "staabm/side-effects-detector": "^1.0.5" }, "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + "ext-soap": "To be able to generate mocks based on WSDL files" }, "bin": [ "phpunit" @@ -16489,7 +18923,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.6-dev" + "dev-main": "11.5-dev" } }, "autoload": { @@ -16521,7 +18955,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.22" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.42" }, "funding": [ { @@ -16532,30 +18966,38 @@ "url": "https://github.com/sebastianbergmann", "type": "github" }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, { "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", "type": "tidelift" } ], - "time": "2024-12-05T13:48:26+00:00" + "time": "2025-09-28T12:09:13+00:00" }, { "name": "rector/rector", - "version": "2.0.6", + "version": "2.2.3", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "fa0cb009dc3df084bf549032ae4080a0481a2036" + "reference": "d27f976a332a87b5d03553c2e6f04adbe5da034f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/fa0cb009dc3df084bf549032ae4080a0481a2036", - "reference": "fa0cb009dc3df084bf549032ae4080a0481a2036", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/d27f976a332a87b5d03553c2e6f04adbe5da034f", + "reference": "d27f976a332a87b5d03553c2e6f04adbe5da034f", "shasum": "" }, "require": { "php": "^7.4|^8.0", - "phpstan/phpstan": "^2.1.1" + "phpstan/phpstan": "^2.1.26" }, "conflict": { "rector/rector-doctrine": "*", @@ -16580,6 +19022,7 @@ "MIT" ], "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "homepage": "https://getrector.com/", "keywords": [ "automation", "dev", @@ -16588,7 +19031,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.0.6" + "source": "https://github.com/rectorphp/rector/tree/2.2.3" }, "funding": [ { @@ -16596,7 +19039,7 @@ "type": "github" } ], - "time": "2025-01-06T10:38:36+00:00" + "time": "2025-10-11T21:50:23+00:00" }, { "name": "roave/security-advisories", @@ -16604,18 +19047,19 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "6ec01b072baedc0e230b90c70e521007851c8f7c" + "reference": "7a8f128281289412092c450a5eb3df5cabbc89e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/6ec01b072baedc0e230b90c70e521007851c8f7c", - "reference": "6ec01b072baedc0e230b90c70e521007851c8f7c", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/7a8f128281289412092c450a5eb3df5cabbc89e1", + "reference": "7a8f128281289412092c450a5eb3df5cabbc89e1", "shasum": "" }, "conflict": { "3f/pygmentize": "<1.2", + "adaptcms/adaptcms": "<=1.3", "admidio/admidio": "<4.3.12", - "adodb/adodb-php": "<=5.20.20|>=5.21,<=5.21.3", + "adodb/adodb-php": "<=5.22.9", "aheinze/cockpit": "<2.2", "aimeos/ai-admin-graphql": ">=2022.04.1,<2022.10.10|>=2023.04.1,<2023.10.6|>=2024.04.1,<2024.07.2", "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", @@ -16626,7 +19070,8 @@ "airesvsg/acf-to-rest-api": "<=3.1", "akaunting/akaunting": "<2.1.13", "akeneo/pim-community-dev": "<5.0.119|>=6,<6.0.53", - "alextselegidis/easyappointments": "<1.5", + "alextselegidis/easyappointments": "<1.5.2.0-beta1", + "alt-design/alt-redirect": "<1.6.4", "alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1", "amazing/media2click": ">=1,<1.3.3", "ameos/ameos_tarteaucitron": "<1.2.23", @@ -16636,9 +19081,11 @@ "anchorcms/anchor-cms": "<=0.12.7", "andreapollastri/cipi": "<=3.1.15", "andrewhaine/silverstripe-form-capture": ">=0.2,<=0.2.3|>=1,<1.0.2|>=2,<2.2.5", + "aoe/restler": "<1.7.1", "apache-solr-for-typo3/solr": "<2.8.3", "apereo/phpcas": "<1.6", - "api-platform/core": ">=2.2,<2.2.10|>=2.3,<2.3.6|>=2.6,<2.7.10|>=3,<3.0.12|>=3.1,<3.1.3", + "api-platform/core": "<3.4.17|>=4,<4.0.22|>=4.1,<4.1.5", + "api-platform/graphql": "<3.4.17|>=4,<4.0.22|>=4.1,<4.1.5", "appwrite/server-ce": "<=1.2.1", "arc/web": "<3", "area17/twill": "<1.2.5|>=2,<2.5.3", @@ -16646,30 +19093,38 @@ "asymmetricrypt/asymmetricrypt": "<9.9.99", "athlon1600/php-proxy": "<=5.1", "athlon1600/php-proxy-app": "<=3", + "athlon1600/youtube-downloader": "<=4", "austintoddj/canvas": "<=3.4.2", - "auth0/wordpress": "<=4.6", + "auth0/auth0-php": ">=3.3,<=8.16", + "auth0/login": "<=7.18", + "auth0/symfony": "<=5.4.1", + "auth0/wordpress": "<=5.3", "automad/automad": "<2.0.0.0-alpha5", "automattic/jetpack": "<9.8", "awesome-support/awesome-support": "<=6.0.7", "aws/aws-sdk-php": "<3.288.1", "azuracast/azuracast": "<0.18.3", + "b13/seo_basics": "<0.8.2", "backdrop/backdrop": "<1.27.3|>=1.28,<1.28.2", "backpack/crud": "<3.4.9", "backpack/filemanager": "<2.0.2|>=3,<3.0.9", - "bacula-web/bacula-web": "<8.0.0.0-RC2-dev", - "badaso/core": "<2.7", - "bagisto/bagisto": "<2.1", + "bacula-web/bacula-web": "<9.7.1", + "badaso/core": "<=2.9.11", + "bagisto/bagisto": "<=2.3.7", "barrelstrength/sprout-base-email": "<1.2.7", "barrelstrength/sprout-forms": "<3.9", - "barryvdh/laravel-translation-manager": "<0.6.2", + "barryvdh/laravel-translation-manager": "<0.6.8", "barzahlen/barzahlen-php": "<2.0.1", "baserproject/basercms": "<=5.1.1", "bassjobsen/bootstrap-3-typeahead": ">4.0.2", "bbpress/bbpress": "<2.6.5", + "bcit-ci/codeigniter": "<3.1.3", "bcosca/fatfree": "<3.7.2", "bedita/bedita": "<4", + "bednee/cooluri": "<1.0.30", "bigfork/silverstripe-form-capture": ">=3,<3.1.1", - "billz/raspap-webgui": "<=3.1.4", + "billz/raspap-webgui": "<3.3.6", + "binarytorch/larecipe": "<2.8.1", "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", @@ -16684,6 +19139,7 @@ "brotkrueml/typo3-matomo-integration": "<1.3.2", "buddypress/buddypress": "<7.2.1", "bugsnag/bugsnag-laravel": ">=2,<2.0.2", + "bvbmedia/multishop": "<2.0.39", "bytefury/crater": "<6.0.2", "cachethq/cachet": "<2.5.1", "cakephp/cakephp": "<3.10.3|>=4,<4.0.10|>=4.1,<4.1.4|>=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10", @@ -16694,32 +19150,39 @@ "cart2quote/module-quotation-encoded": ">=4.1.6,<=4.4.5|>=5,<5.4.4", "cartalyst/sentry": "<=2.1.6", "catfan/medoo": "<1.7.5", - "causal/oidc": "<2.1", + "causal/oidc": "<4", "cecil/cecil": "<7.47.1", "centreon/centreon": "<22.10.15", "cesnet/simplesamlphp-module-proxystatistics": "<3.1", "chriskacerguis/codeigniter-restserver": "<=2.7.1", + "chrome-php/chrome": "<1.14", "civicrm/civicrm-core": ">=4.2,<4.2.9|>=4.3,<4.3.3", - "ckeditor/ckeditor": "<4.24", - "cockpit-hq/cockpit": "<2.7|==2.7", + "ckeditor/ckeditor": "<4.25", + "clickstorm/cs-seo": ">=6,<6.8|>=7,<7.5|>=8,<8.4|>=9,<9.3", + "co-stack/fal_sftp": "<0.2.6", + "cockpit-hq/cockpit": "<2.11.4", "codeception/codeception": "<3.1.3|>=4,<4.1.22", - "codeigniter/framework": "<3.1.9", - "codeigniter4/framework": "<4.4.7", + "codeigniter/framework": "<3.1.10", + "codeigniter4/framework": "<4.6.2", "codeigniter4/shield": "<1.0.0.0-beta8", "codiad/codiad": "<=2.8.4", + "codingms/additional-tca": ">=1.7,<1.15.17|>=1.16,<1.16.9", + "commerceteam/commerce": ">=0.9.6,<0.9.9", + "components/jquery": ">=1.0.3,<3.5", "composer/composer": "<1.10.27|>=2,<2.2.24|>=2.3,<2.7.7", - "concrete5/concrete5": "<9.3.4", + "concrete5/concrete5": "<9.4.3", "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": "<=5.4.1", + "contao/contao": ">=3,<3.5.37|>=4,<4.4.56|>=4.5,<4.13.56|>=5,<5.3.38|>=5.4.0.0-RC1-dev,<5.6.1", "contao/core": "<3.5.39", - "contao/core-bundle": "<4.13.49|>=5,<5.3.15|>=5.4,<5.4.3", + "contao/core-bundle": "<4.13.56|>=5,<5.3.38|>=5.4,<5.6.1", "contao/listing-bundle": ">=3,<=3.5.30|>=4,<4.4.8", "contao/managed-edition": "<=1.5", "corveda/phpsandbox": "<1.3.5", "cosenary/instagram": "<=2.3", - "craftcms/cms": "<4.13.2|>=5,<5.5.2", + "couleurcitron/tarteaucitron-wp": "<0.3", + "craftcms/cms": "<=4.16.5|>=5,<=5.8.6", "croogo/croogo": "<4", "cuyz/valinor": "<0.12", "czim/file-handling": "<1.5|>=2,<2.3", @@ -16728,6 +19191,7 @@ "dapphp/securimage": "<3.6.6", "darylldoyle/safe-svg": "<1.9.10", "datadog/dd-trace": ">=0.30,<0.30.2", + "datahihi1/tiny-env": "<1.0.3|>=1.0.9,<1.0.11", "datatables/datatables": "<1.10.10", "david-garcia/phpwhois": "<=4.3.1", "dbrisinajumi/d2files": "<1", @@ -16737,7 +19201,11 @@ "desperado/xml-bundle": "<=0.1.7", "dev-lancer/minecraft-motd-parser": "<=1.0.5", "devgroup/dotplant": "<2020.09.14-dev", + "digimix/wp-svg-upload": "<=1", "directmailteam/direct-mail": "<6.0.3|>=7,<7.0.3|>=8,<9.5.2", + "dl/yag": "<3.0.1", + "dmk/webkitpdf": "<1.1.4", + "dnadesign/silverstripe-elemental": "<5.3.12", "doctrine/annotations": "<1.2.7", "doctrine/cache": ">=1,<1.3.2|>=1.4,<1.4.2", "doctrine/common": "<2.4.3|>=2.5,<2.5.1", @@ -16747,12 +19215,34 @@ "doctrine/mongodb-odm": "<1.0.2", "doctrine/mongodb-odm-bundle": "<3.0.1", "doctrine/orm": ">=1,<1.2.4|>=2,<2.4.8|>=2.5,<2.5.1|>=2.8.3,<2.8.4", - "dolibarr/dolibarr": "<19.0.2", + "dolibarr/dolibarr": "<21.0.3", "dompdf/dompdf": "<2.0.4", "doublethreedigital/guest-entries": "<3.1.2", - "drupal/core": ">=6,<6.38|>=7,<7.102|>=8,<10.2.11|>=10.3,<10.3.9|>=11,<11.0.8", + "drupal-pattern-lab/unified-twig-extensions": "<=0.1", + "drupal/admin_audit_trail": "<1.0.5", + "drupal/ai": "<1.0.5", + "drupal/alogin": "<2.0.6", + "drupal/cache_utility": "<1.2.1", + "drupal/commerce_alphabank_redirect": "<1.0.3", + "drupal/commerce_eurobank_redirect": "<2.1.1", + "drupal/config_split": "<1.10|>=2,<2.0.2", + "drupal/core": ">=6,<6.38|>=7,<7.102|>=8,<10.3.14|>=10.4,<10.4.5|>=11,<11.0.13|>=11.1,<11.1.5", "drupal/core-recommended": ">=7,<7.102|>=8,<10.2.11|>=10.3,<10.3.9|>=11,<11.0.8", "drupal/drupal": ">=5,<5.11|>=6,<6.38|>=7,<7.102|>=8,<10.2.11|>=10.3,<10.3.9|>=11,<11.0.8", + "drupal/formatter_suite": "<2.1", + "drupal/gdpr": "<3.0.1|>=3.1,<3.1.2", + "drupal/google_tag": "<1.8|>=2,<2.0.8", + "drupal/ignition": "<1.0.4", + "drupal/lightgallery": "<1.6", + "drupal/link_field_display_mode_formatter": "<1.6", + "drupal/matomo": "<1.24", + "drupal/oauth2_client": "<4.1.3", + "drupal/oauth2_server": "<2.1", + "drupal/obfuscate": "<2.0.1", + "drupal/quick_node_block": "<2", + "drupal/rapidoc_elements_field_formatter": "<1.0.1", + "drupal/spamspan": "<3.2.1", + "drupal/tfa": "<1.10", "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", @@ -16762,10 +19252,11 @@ "elefant/cms": "<2.0.7", "elgg/elgg": "<3.3.24|>=4,<4.0.5", "elijaa/phpmemcacheadmin": "<=1.3", + "elmsln/haxcms": "<11.0.14", "encore/laravel-admin": "<=1.8.19", "endroid/qr-code-bundle": "<3.4.2", "enhavo/enhavo-app": "<=0.13.1", - "enshrined/svg-sanitize": "<0.15", + "enshrined/svg-sanitize": "<0.22", "erusev/parsedown": "<1.7.2", "ether/logs": "<3.0.4", "evolutioncms/evolution": "<=3.2.3", @@ -16776,13 +19267,13 @@ "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|>=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-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6|>=1.5,<1.5.29|>=2.3,<2.3.39|>=3.3,<3.3.39", + "ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2.1|>=5,<5.0.1|>=5.1,<5.1.1|>=5.3.0.0-beta1,<5.3.5", "ezsystems/ezplatform-graphql": ">=1.0.0.0-RC1-dev,<1.0.13|>=2.0.0.0-beta1,<2.3.12", "ezsystems/ezplatform-http-cache": "<2.3.16", "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|>=3.3,<3.3.40", + "ezsystems/ezplatform-richtext": ">=2.3,<2.3.26|>=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", @@ -16805,10 +19296,10 @@ "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", + "fixpunkt/fp-newsletter": "<1.1.1|>=1.2,<2.1.2|>=2.2,<3.2.6", + "flarum/core": "<1.8.10", "flarum/flarum": "<0.1.0.0-beta8", - "flarum/framework": "<1.8.5", + "flarum/framework": "<1.8.10", "flarum/mentions": "<1.6.3", "flarum/sticky": ">=0.1.0.0-beta14,<=0.1.0.0-beta15", "flarum/tags": "<=0.1.0.0-beta13", @@ -16829,25 +19320,29 @@ "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.2.0.0-RC3", + "froala/wysiwyg-editor": "<=4.3", + "froxlor/froxlor": "<=2.2.5", "frozennode/administrator": "<=5.0.12", "fuel/core": "<1.8.1", "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", + "georgringer/news": "<1.3.3", + "geshi/geshi": "<=1.0.9.1", + "getformwork/formwork": "<1.13.1|>=2.0.0.0-beta1,<2.0.0.0-beta4", "getgrav/grav": "<1.7.46", - "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/cms": "<3.9.8.3-dev|>=3.10,<3.10.1.2-dev|>=4,<4.7.1", + "getkirby/kirby": "<3.9.8.3-dev|>=3.10,<3.10.1.2-dev|>=4,<4.7.1", "getkirby/panel": "<2.5.14", "getkirby/starterkit": "<=3.7.0.2", "gilacms/gila": "<=1.15.4", "gleez/cms": "<=1.3|==2", "globalpayments/php-sdk": "<2", + "goalgorilla/open_social": "<12.3.11|>=12.4,<12.4.10|>=13.0.0.0-alpha1,<13.0.0.0-alpha11", "gogentooss/samlbase": "<1.2.7", - "google/protobuf": "<3.15", + "google/protobuf": "<3.4", "gos/web-socket-bundle": "<1.10.4|>=2,<2.6.1|>=3,<3.3", + "gp247/core": "<1.1.24", "gree/jose": "<2.2.1", "gregwar/rst": "<1.0.3", "grumpydictator/firefly-iii": "<6.1.17", @@ -16856,6 +19351,7 @@ "guzzlehttp/oauth-subscriber": "<0.8.1", "guzzlehttp/psr7": "<1.9.1|>=2,<2.4.5", "haffner/jh_captcha": "<=2.1.3|>=3,<=3.0.2", + "handcraftedinthealps/goodby-csv": "<1.4.3", "harvesthq/chosen": "<1.8.7", "helloxz/imgurl": "<=2.31", "hhxsv5/laravel-s": "<3.7.36", @@ -16865,14 +19361,15 @@ "hov/jobfair": "<1.0.13|>=2,<2.0.2", "httpsoft/http-message": "<1.0.12", "hyn/multi-tenant": ">=5.6,<5.7.2", - "ibexa/admin-ui": ">=4.2,<4.2.3|>=4.6,<4.6.14", + "ibexa/admin-ui": ">=4.2,<4.2.3|>=4.6,<4.6.25|>=5,<5.0.3", + "ibexa/admin-ui-assets": ">=4.6.0.0-alpha1,<4.6.21", "ibexa/core": ">=4,<4.0.7|>=4.1,<4.1.4|>=4.2,<4.2.3|>=4.5,<4.5.6|>=4.6,<4.6.2", - "ibexa/fieldtype-richtext": ">=4.6,<4.6.10", + "ibexa/fieldtype-richtext": ">=4.6,<4.6.25|>=5,<5.0.3", "ibexa/graphql": ">=2.5,<2.5.31|>=3.3,<3.3.28|>=4.2,<4.2.3", "ibexa/http-cache": ">=4.6,<4.6.14", "ibexa/post-install": "<1.0.16|>=4.6,<4.6.14", "ibexa/solr": ">=4.5,<4.5.4", - "ibexa/user": ">=4,<4.4.3", + "ibexa/user": ">=4,<4.4.3|>=5,<5.0.3", "icecoder/icecoder": "<=8.1", "idno/known": "<=1.3.1", "ilicmiljan/secure-props": ">=1.2,<1.2.2", @@ -16883,37 +19380,43 @@ "illuminate/view": "<6.20.42|>=7,<7.30.6|>=8,<8.75", "imdbphp/imdbphp": "<=5.1.1", "impresscms/impresscms": "<=1.4.5", - "impresspages/impresspages": "<=1.0.12", - "in2code/femanager": "<5.5.3|>=6,<6.3.4|>=7,<7.2.3", + "impresspages/impresspages": "<1.0.13", + "in2code/femanager": "<6.4.2|>=7,<7.5.3|>=8,<8.3.1", "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", + "in2code/powermail": "<7.5.1|>=8,<8.5.1|>=9,<10.9.1|>=11,<12.5.3|==13", "innologi/typo3-appointments": "<2.0.6", "intelliants/subrion": "<4.2.2", "inter-mediator/inter-mediator": "==5.5", "ipl/web": "<0.10.1", + "islandora/crayfish": "<4.1", "islandora/islandora": ">=2,<2.4.1", "ivankristianto/phpwhois": "<=4.3", "jackalope/jackalope-doctrine-dbal": "<1.7.4", + "jambagecom/div2007": "<0.10.2", "james-heinrich/getid3": "<1.9.21", - "james-heinrich/phpthumb": "<1.7.12", + "james-heinrich/phpthumb": "<=1.7.23", "jasig/phpcas": "<1.3.3", + "jbartels/wec-map": "<3.0.3", "jcbrand/converse.js": "<3.3.3", "joelbutcher/socialstream": "<5.6|>=6,<6.2", - "johnbillion/wp-crontrol": "<1.16.2", + "johnbillion/wp-crontrol": "<1.16.2|>=1.17,<1.19.2", "joomla/application": "<1.0.13", "joomla/archive": "<1.1.12|>=2,<2.0.1", + "joomla/database": ">=1,<2.2|>=3,<3.4", "joomla/filesystem": "<1.6.2|>=2,<2.0.1", - "joomla/filter": "<1.4.4|>=2,<2.0.1", + "joomla/filter": "<2.0.6|>=3,<3.0.5|==4", "joomla/framework": "<1.5.7|>=2.5.4,<=3.8.12", "joomla/input": ">=2,<2.0.2", - "joomla/joomla-cms": ">=2.5,<3.9.12", + "joomla/joomla-cms": "<3.9.12|>=4,<4.4.13|>=5,<5.2.6", + "joomla/joomla-platform": "<1.5.4", "joomla/session": "<1.3.1", "joyqi/hyper-down": "<=2.4.27", "jsdecena/laracom": "<2.0.9", "jsmitty12/phpwhois": "<5.1", - "juzaweb/cms": "<=3.4", + "juzaweb/cms": "<=3.4.2", "jweiland/events2": "<8.3.8|>=9,<9.0.6", + "jweiland/kk-downloader": "<1.2.2", "kazist/phpwhois": "<=4.2.6", "kelvinmo/simplexrd": "<3.1.1", "kevinpapst/kimai2": "<1.16.7", @@ -16923,6 +19426,7 @@ "klaviyo/magento2-extension": ">=1,<3", "knplabs/knp-snappy": "<=1.4.2", "kohana/core": "<3.3.3", + "koillection/koillection": "<1.6.12", "krayin/laravel-crm": "<=1.3", "kreait/firebase-php": ">=3.2,<3.8.1", "kumbiaphp/kumbiapp": "<=1.1.1", @@ -16933,17 +19437,19 @@ "lara-zeus/artemis": ">=1,<=1.0.6", "lara-zeus/dynamic-dashboard": ">=3,<=3.0.1", "laravel/fortify": "<1.11.1", - "laravel/framework": "<6.20.45|>=7,<7.30.7|>=8,<8.83.28|>=9,<9.52.17|>=10,<10.48.23|>=11,<11.31", + "laravel/framework": "<10.48.29|>=11,<11.44.1|>=12,<12.1.1", "laravel/laravel": ">=5.4,<5.4.22", "laravel/pulse": "<1.3.1", "laravel/reverb": "<1.4", "laravel/socialite": ">=1,<2.0.10", "latte/latte": "<2.10.8", "lavalite/cms": "<=9|==10.1", + "lavitto/typo3-form-to-database": "<2.2.5|>=3,<3.2.2|>=4,<4.2.3|>=5,<5.0.2", "lcobucci/jwt": ">=3.4,<3.4.6|>=4,<4.0.4|>=4.1,<4.1.5", - "league/commonmark": "<2.6", + "league/commonmark": "<2.7", "league/flysystem": "<1.1.4|>=2,<2.1.1", "league/oauth2-server": ">=8.3.2,<8.4.2|>=8.5,<8.5.3", + "leantime/leantime": "<3.3", "lexik/jwt-authentication-bundle": "<2.10.7|>=2.11,<2.11.3", "libreform/libreform": ">=2,<=2.0.8", "librenms/librenms": "<2017.08.18", @@ -16951,49 +19457,63 @@ "lightsaml/lightsaml": "<1.3.5", "limesurvey/limesurvey": "<6.5.12", "livehelperchat/livehelperchat": "<=3.91", - "livewire/livewire": "<2.12.7|>=3.0.0.0-beta1,<3.5.2", + "livewire/livewire": "<2.12.7|>=3.0.0.0-beta1,<3.6.4", + "livewire/volt": "<1.7", "lms/routes": "<2.1.1", "localizationteam/l10nmgr": "<7.4|>=8,<8.7|>=9,<9.2", + "lomkit/laravel-rest-api": "<2.13", + "luracast/restler": "<3.1", "luyadev/yii-helpers": "<1.2.1", + "macropay-solutions/laravel-crud-wizard-free": "<3.4.17", "maestroerror/php-heic-to-jpg": "<1.0.5", - "magento/community-edition": "<2.4.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/community-edition": "<=2.4.5.0-patch14|==2.4.6|>=2.4.6.0-patch1,<=2.4.6.0-patch12|>=2.4.7.0-beta1,<=2.4.7.0-patch7|>=2.4.8.0-beta1,<=2.4.8.0-patch2|>=2.4.9.0-alpha1,<=2.4.9.0-alpha2|==2.4.9", "magento/core": "<=1.9.4.5", "magento/magento1ce": "<1.9.4.3-dev", "magento/magento1ee": ">=1,<1.14.4.3-dev", "magento/product-community-edition": "<2.4.4.0-patch9|>=2.4.5,<2.4.5.0-patch8|>=2.4.6,<2.4.6.0-patch6|>=2.4.7,<2.4.7.0-patch1", + "magento/project-community-edition": "<=2.0.2", "magneto/core": "<1.9.4.4-dev", + "mahocommerce/maho": "<25.9", "maikuolan/phpmussel": ">=1,<1.6", "mainwp/mainwp": "<=4.4.3.3", + "manogi/nova-tiptap": "<=3.2.6", "mantisbt/mantisbt": "<=2.26.3", "marcwillmann/turn": "<0.3.3", + "marshmallow/nova-tiptap": "<5.7", + "matomo/matomo": "<1.11", "matyhtf/framework": "<3.0.6", - "mautic/core": "<4.4.13|>=5,<5.1.1", + "mautic/core": "<5.2.8|>=6.0.0.0-alpha,<6.0.5", "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/abuse-filter": "<1.39.9|>=1.40,<1.41.3|>=1.42,<1.42.2", "mediawiki/cargo": "<3.6.1", "mediawiki/core": "<1.39.5|==1.40", + "mediawiki/data-transfer": ">=1.39,<1.39.11|>=1.41,<1.41.3|>=1.42,<1.42.2", "mediawiki/matomo": "<2.4.3", "mediawiki/semantic-media-wiki": "<4.0.2", + "mehrwert/phpmyadmin": "<3.2", "melisplatform/melis-asset-manager": "<5.0.1", - "melisplatform/melis-cms": "<5.0.1", + "melisplatform/melis-cms": "<5.3.4", + "melisplatform/melis-cms-slider": "<5.3.1", + "melisplatform/melis-core": "<5.3.11", "melisplatform/melis-front": "<5.0.1", "mezzio/mezzio-swoole": "<3.7|>=4,<4.3", "mgallegos/laravel-jqgrid": "<=1.3", "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.16", + "microweber/microweber": "<=2.0.19", "mikehaertl/php-shellcommand": "<1.6.1", "miniorange/miniorange-saml": "<1.4.3", "mittwald/typo3_forum": "<1.2.1", "mobiledetect/mobiledetectlib": "<2.8.32", - "modx/revolution": "<=2.8.3.0-patch", + "modx/revolution": "<=3.1", "mojo42/jirafeau": "<4.4", "mongodb/mongodb": ">=1,<1.9.2", "monolog/monolog": ">=1.8,<1.12", - "moodle/moodle": "<4.3.8|>=4.4,<4.4.4", + "moodle/moodle": "<4.3.12|>=4.4,<4.4.8|>=4.5.0.0-beta,<4.5.4", + "moonshine/moonshine": "<=3.12.5", "mos/cimage": "<0.7.19", "movim/moxl": ">=0.8,<=0.10", "movingbytes/social-network": "<=1.2.1", @@ -17005,7 +19525,9 @@ "munkireport/reportdata": "<3.5", "munkireport/softwareupdate": "<1.6", "mustache/mustache": ">=2,<2.14.1", + "mwdelaney/wp-enable-svg": "<=0.2", "namshi/jose": "<2.2", + "nasirkhan/laravel-starter": "<11.11", "nategood/httpful": "<1", "neoan3-apps/template": "<1.1.1", "neorazorx/facturascripts": "<2022.04", @@ -17014,14 +19536,18 @@ "neos/media-browser": "<7.3.19|>=8,<8.0.16|>=8.1,<8.1.11|>=8.2,<8.2.11|>=8.3,<8.3.9", "neos/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<5.3.10|>=7,<7.0.9|>=7.1,<7.1.7|>=7.2,<7.2.6|>=7.3,<7.3.4|>=8,<8.0.2", "neos/swiftmailer": "<5.4.5", + "nesbot/carbon": "<2.72.6|>=3,<3.8.4", + "netcarver/textile": "<=4.1.2", "netgen/tagsbundle": ">=3.4,<3.4.11|>=4,<4.0.15", "nette/application": ">=2,<2.0.19|>=2.1,<2.1.13|>=2.2,<2.2.10|>=2.3,<2.3.14|>=2.4,<2.4.16|>=3,<3.0.6", "nette/nette": ">=2,<2.0.19|>=2.1,<2.1.13", "nilsteampassnet/teampass": "<3.1.3.1-dev", + "nitsan/ns-backup": "<13.0.1", "nonfiction/nterchange": "<4.1.1", "notrinos/notrinos-erp": "<=0.7", "noumo/easyii": "<=0.9", "novaksolutions/infusionsoft-php-sdk": "<1", + "novosga/novosga": "<=2.2.12", "nukeviet/nukeviet": "<4.5.02", "nyholm/psr7": "<1.6.1", "nystudio107/craft-seomatic": "<3.4.12", @@ -17029,18 +19555,19 @@ "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.6.4", + "october/october": "<3.7.5", "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.15", + "october/system": "<3.7.5", + "oliverklee/phpunit": "<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", + "open-web-analytics/open-web-analytics": "<1.8.1", "opencart/opencart": ">=0", "openid/php-openid": "<2.3", - "openmage/magento-lts": "<20.10.1", + "openmage/magento-lts": "<20.12.3", "opensolutions/vimbadmin": "<=3.0.15", - "opensource-workshop/connect-cms": "<1.7.2|>=2,<2.3.2", + "opensource-workshop/connect-cms": "<1.8.7|>=2,<2.4.7", "orchid/platform": ">=8,<14.43", "oro/calendar-bundle": ">=4.2,<=4.2.6|>=5,<=5.0.6|>=5.1,<5.1.1", "oro/commerce": ">=4.1,<5.0.11|>=5.1,<5.1.1", @@ -17049,7 +19576,7 @@ "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/oxideshop-ce": "<=7.0.5", "oxid-esales/paymorrow-module": ">=1,<1.0.2|>=2,<2.0.1", "packbackbooks/lti-1-3-php-library": "<5", "padraic/humbug_get_contents": "<1.1.2", @@ -17065,6 +19592,7 @@ "pear/archive_tar": "<1.4.14", "pear/auth": "<1.2.4", "pear/crypt_gpg": "<1.6.7", + "pear/http_request2": "<2.7", "pear/pear": "<=1.10.1", "pegasus/google-for-jobs": "<1.5.1|>=2,<2.1.1", "personnummer/personnummer": "<3.0.2", @@ -17077,11 +19605,12 @@ "phpfastcache/phpfastcache": "<6.1.5|>=7,<7.1.2|>=8,<8.0.7", "phpmailer/phpmailer": "<6.5", "phpmussel/phpmussel": ">=1,<1.6", - "phpmyadmin/phpmyadmin": "<5.2.1", + "phpmyadmin/phpmyadmin": "<5.2.2", "phpmyfaq/phpmyfaq": "<3.2.5|==3.2.5|>=3.2.10,<=4.0.1", "phpoffice/common": "<0.2.9", - "phpoffice/phpexcel": "<1.8.1", - "phpoffice/phpspreadsheet": "<=1.29.6|>=2,<=2.1.5|>=2.2,<=2.3.4|>=3,<3.7", + "phpoffice/math": "<=0.2", + "phpoffice/phpexcel": "<=1.8.2", + "phpoffice/phpspreadsheet": "<1.30|>=2,<2.1.12|>=2.2,<2.4|>=3,<3.10|>=4,<5", "phpseclib/phpseclib": "<2.0.47|>=3,<3.0.36", "phpservermon/phpservermon": "<3.6", "phpsysinfo/phpsysinfo": "<3.4.3", @@ -17090,18 +19619,19 @@ "phpxmlrpc/extras": "<0.6.1", "phpxmlrpc/phpxmlrpc": "<4.9.2", "pi/pi": "<=2.5", - "pimcore/admin-ui-classic-bundle": "<1.5.4", - "pimcore/customer-management-framework-bundle": "<4.0.6", + "pimcore/admin-ui-classic-bundle": "<1.7.6", + "pimcore/customer-management-framework-bundle": "<4.2.1", "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", - "pimcore/pimcore": "<11.2.4", - "pixelfed/pixelfed": "<0.11.11", + "pimcore/pimcore": "<11.5.4", + "piwik/piwik": "<1.11", + "pixelfed/pixelfed": "<0.12.5", "plotly/plotly.js": "<2.25.2", "pocketmine/bedrock-protocol": "<8.0.2", - "pocketmine/pocketmine-mp": "<5.11.2", + "pocketmine/pocketmine-mp": "<5.32.1", "pocketmine/raklib": ">=0.14,<0.14.6|>=0.15,<0.15.1", "pressbooks/pressbooks": "<5.18", "prestashop/autoupgrade": ">=4,<4.10.1", @@ -17109,8 +19639,10 @@ "prestashop/blockwishlist": ">=2,<2.1.1", "prestashop/contactform": ">=1.0.1,<4.3", "prestashop/gamification": "<2.3.2", - "prestashop/prestashop": "<8.1.6", + "prestashop/prestashop": "<8.2.3", "prestashop/productcomments": "<5.0.2", + "prestashop/ps_checkout": "<4.4.1|>=5,<5.0.5", + "prestashop/ps_contactinfo": "<=3.3.2", "prestashop/ps_emailsubscription": "<2.6.1", "prestashop/ps_facetedsearch": "<3.4.1", "prestashop/ps_linklist": "<3.1", @@ -17118,10 +19650,11 @@ "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.8", + "pterodactyl/panel": "<=1.11.10", "ptheofan/yii2-statemachine": ">=2.0.0.0-RC1-dev,<=2", "ptrofimov/beanstalk_console": "<1.7.14", "pubnub/pubnub": "<6.1", + "punktde/pt_extbase": "<1.5.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", @@ -17135,30 +19668,34 @@ "rap2hpoutre/laravel-log-viewer": "<0.13", "react/http": ">=0.7,<1.9", "really-simple-plugins/complianz-gdpr": "<6.4.2", - "redaxo/source": "<5.18", + "redaxo/source": "<5.18.3", "remdex/livehelperchat": "<4.29", + "renolit/reint-downloadmanager": "<4.0.2|>=5,<5.0.1", "reportico-web/reportico": "<=8.1", "rhukster/dom-sanitizer": "<1.0.7", "rmccue/requests": ">=1.6,<1.8", "robrichards/xmlseclibs": ">=1,<3.0.4", "roots/soil": "<4.1", + "roundcube/roundcubemail": "<1.5.10|>=1.6,<1.6.11", "rudloff/alltube": "<3.0.3", - "s-cart/core": "<6.9", + "rudloff/rtmpdump-bin": "<=2.3.1", + "s-cart/core": "<=9.0.5", "s-cart/s-cart": "<6.9", "sabberworm/php-css-parser": ">=1,<1.0.1|>=2,<2.0.1|>=3,<3.0.1|>=4,<4.0.1|>=5,<5.0.9|>=5.1,<5.1.3|>=5.2,<5.2.1|>=6,<6.0.2|>=7,<7.0.4|>=8,<8.0.1|>=8.1,<8.1.1|>=8.2,<8.2.1|>=8.3,<8.3.1", "sabre/dav": ">=1.6,<1.7.11|>=1.8,<1.8.9", - "samwilson/unlinked-wikibase": "<1.39.6|>=1.40,<1.40.2|>=1.41,<1.41.1", + "samwilson/unlinked-wikibase": "<1.42", "scheb/two-factor-bundle": "<3.26|>=4,<4.11", "sensiolabs/connect": "<4.2.3", "serluck/phpwhois": "<=4.2.6", + "setasign/fpdi": "<2.6.4", "sfroemken/url_redirect": "<=1.2.1", "sheng/yiicms": "<1.2.1", - "shopware/core": "<=6.5.8.12|>=6.6,<=6.6.5", - "shopware/platform": "<=6.5.8.12|>=6.6,<=6.6.5", + "shopware/core": "<6.5.8.18-dev|>=6.6,<6.6.10.3-dev|>=6.7,<6.7.2.1-dev", + "shopware/platform": "<=6.6.10.4|>=6.7.0.0-RC1-dev,<6.7.0.0-RC2-dev", "shopware/production": "<=6.3.5.2", - "shopware/shopware": "<=5.7.17", + "shopware/shopware": "<=5.7.17|>=6.7,<6.7.2.1-dev", "shopware/storefront": "<=6.4.8.1|>=6.5.8,<6.5.8.7-dev", - "shopxo/shopxo": "<=6.1", + "shopxo/shopxo": "<=6.4", "showdoc/showdoc": "<2.10.4", "shuchkin/simplexlsx": ">=1.0.12,<1.1.13", "silverstripe-australia/advancedreports": ">=1,<=2", @@ -17167,7 +19704,7 @@ "silverstripe/cms": "<4.11.3", "silverstripe/comments": ">=1.3,<3.1.1", "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", - "silverstripe/framework": "<5.2.16", + "silverstripe/framework": "<5.3.23", "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", @@ -17179,9 +19716,10 @@ "silverstripe/taxonomy": ">=1.3,<1.3.1|>=2,<2.0.1", "silverstripe/userforms": "<3|>=5,<5.4.2", "silverstripe/versioned-admin": ">=1,<1.11.1", + "simogeo/filemanager": "<=2.5", "simple-updates/phpwhois": "<=1", - "simplesamlphp/saml2": "<4.6.14|==5.0.0.0-alpha12", - "simplesamlphp/saml2-legacy": "<4.6.14", + "simplesamlphp/saml2": "<=4.16.15|>=5.0.0.0-alpha1,<=5.0.0.0-alpha19", + "simplesamlphp/saml2-legacy": "<=4.16.15", "simplesamlphp/simplesamlphp": "<1.18.6", "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1", "simplesamlphp/simplesamlphp-module-openid": "<1", @@ -17190,15 +19728,19 @@ "simplesamlphp/xml-security": "==1.6.11", "simplito/elliptic-php": "<1.0.6", "sitegeist/fluid-components": "<3.5", + "sjbr/sr-feuser-register": "<2.6.2|>=5.1,<12.5", "sjbr/sr-freecap": "<2.4.6|>=2.5,<2.5.3", + "sjbr/static-info-tables": "<2.3.1", "slim/psr7": "<1.4.1|>=1.5,<1.5.1|>=1.6,<1.6.1", "slim/slim": "<2.6", "slub/slub-events": "<3.0.3", "smarty/smarty": "<4.5.3|>=5,<5.1.1", - "snipe/snipe-it": "<=7.0.13", + "snipe/snipe-it": "<8.1.18", "socalnick/scn-social-auth": "<1.15.2", "socialiteproviders/steam": "<1.1", - "spatie/browsershot": "<5.0.3", + "solspace/craft-freeform": ">=5,<5.10.16", + "soosyze/soosyze": "<=2", + "spatie/browsershot": "<5.0.5", "spatie/image-optimizer": "<1.7.3", "spencer14420/sp-php-email-handler": "<1", "spipu/html2pdf": "<5.2.8", @@ -17206,8 +19748,10 @@ "spoonity/tcpdf": "<6.2.22", "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", "ssddanbrown/bookstack": "<24.05.1", - "starcitizentools/citizen-skin": ">=2.6.3,<2.31", - "starcitizentools/tabber-neue": ">=1.9.1,<2.7.2", + "starcitizentools/citizen-skin": ">=1.9.4,<3.4", + "starcitizentools/short-description": ">=4,<4.0.1", + "starcitizentools/tabber-neue": ">=1.9.1,<2.7.2|>=3,<3.1.1", + "starcitizenwiki/embedvideo": "<=4", "statamic/cms": "<=5.16", "stormpath/sdk": "<9.9.99", "studio-42/elfinder": "<=2.1.64", @@ -17215,16 +19759,17 @@ "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.5.21|>=2.6,<2.6.5", + "sulu/sulu": "<1.6.44|>=2,<2.5.25|>=2.6,<2.6.9|>=3.0.0.0-alpha1,<3.0.0.0-alpha3", "sumocoders/framework-user-bundle": "<1.4", "superbig/craft-audit": "<3.0.2", + "svewap/a21glossary": "<=0.4.10", "swag/paypal": "<5.4.4", "swiftmailer/swiftmailer": "<6.2.5", "swiftyedit/swiftyedit": "<1.2", "sylius/admin-bundle": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2", "sylius/grid": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1", "sylius/grid-bundle": "<1.10.1", - "sylius/paypal-plugin": ">=1,<1.2.4|>=1.3,<1.3.1", + "sylius/paypal-plugin": "<1.6.2|>=1.7,<1.7.2|>=2,<2.0.2", "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.12.19|>=1.13.0.0-alpha1,<1.13.4", "symbiote/silverstripe-multivaluefield": ">=3,<3.1", @@ -17261,6 +19806,8 @@ "symfony/translation": ">=2,<2.0.17", "symfony/twig-bridge": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8", "symfony/ux-autocomplete": "<2.11.2", + "symfony/ux-live-component": "<2.25.1", + "symfony/ux-twig-component": "<2.25.1", "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", @@ -17270,8 +19817,8 @@ "t3/dce": "<0.11.5|>=2.2,<2.6.2", "t3g/svg-sanitizer": "<1.0.3", "t3s/content-consent": "<1.0.3|>=2,<2.0.2", - "tastyigniter/tastyigniter": "<3.3", - "tcg/voyager": "<=1.4", + "tastyigniter/tastyigniter": "<4", + "tcg/voyager": "<=1.8", "tecnickcom/tc-lib-pdf-font": "<2.6.4", "tecnickcom/tcpdf": "<6.8", "terminal42/contao-tablelookupwizard": "<3.3.5", @@ -17279,7 +19826,7 @@ "thelia/thelia": ">=2.1,<2.1.3", "theonedemon/phpwhois": "<=4.2.5", "thinkcmf/thinkcmf": "<6.0.8", - "thorsten/phpmyfaq": "<=4.0.1", + "thorsten/phpmyfaq": "<=4.0.1|>=4.0.7,<4.0.13", "tikiwiki/tiki-manager": "<=17.1", "timber/timber": ">=0.16.6,<1.23.1|>=1.24,<1.24.1|>=2,<2.1", "tinymce/tinymce": "<7.2", @@ -17295,17 +19842,30 @@ "tribalsystems/zenario": "<=9.7.61188", "truckersmp/phpwhois": "<=4.3.1", "ttskch/pagination-service-provider": "<1", - "twbs/bootstrap": "<=3.4.1|>=4,<=4.6.2", - "twig/twig": "<3.11.2|>=3.12,<3.14.1", + "twbs/bootstrap": "<3.4.1|>=4,<4.3.1", + "twig/twig": "<3.11.2|>=3.12,<3.14.1|>=3.16,<3.19", "typo3/cms": "<9.5.29|>=10,<10.4.35|>=11,<11.5.23|>=12,<12.2", - "typo3/cms-backend": "<4.1.14|>=4.2,<4.2.15|>=4.3,<4.3.7|>=4.4,<4.4.4|>=7,<=7.6.50|>=8,<=8.7.39|>=9,<=9.5.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-backend": "<4.1.14|>=4.2,<4.2.15|>=4.3,<4.3.7|>=4.4,<4.4.4|>=7,<=7.6.50|>=8,<=8.7.39|>=9,<9.5.55|>=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18", + "typo3/cms-belog": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", + "typo3/cms-beuser": ">=9,<9.5.55|>=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18", + "typo3/cms-core": "<=8.7.56|>=9,<9.5.55|>=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18", + "typo3/cms-dashboard": ">=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18", "typo3/cms-extbase": "<6.2.24|>=7,<7.6.8|==8.1.1", + "typo3/cms-extensionmanager": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", + "typo3/cms-felogin": ">=4.2,<4.2.3", "typo3/cms-fluid": "<4.3.4|>=4.4,<4.4.1", - "typo3/cms-form": ">=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1", + "typo3/cms-form": ">=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", "typo3/cms-frontend": "<4.3.9|>=4.4,<4.4.5", - "typo3/cms-install": "<4.1.14|>=4.2,<4.2.16|>=4.3,<4.3.9|>=4.4,<4.4.5|>=12.2,<12.4.8", + "typo3/cms-indexed-search": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", + "typo3/cms-install": "<4.1.14|>=4.2,<4.2.16|>=4.3,<4.3.9|>=4.4,<4.4.5|>=12.2,<12.4.8|==13.4.2", + "typo3/cms-lowlevel": ">=11,<=11.5.41", + "typo3/cms-recordlist": ">=11,<11.5.48", + "typo3/cms-recycler": ">=9,<9.5.55|>=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18", "typo3/cms-rte-ckeditor": ">=9.5,<9.5.42|>=10,<10.4.39|>=11,<11.5.30", + "typo3/cms-scheduler": ">=11,<=11.5.41", + "typo3/cms-setup": ">=9,<=9.5.50|>=10,<=10.4.49|>=11,<=11.5.43|>=12,<=12.4.30|>=13,<=13.4.11", + "typo3/cms-webhooks": ">=12,<=12.4.30|>=13,<=13.4.11", + "typo3/cms-workspaces": ">=9,<9.5.55|>=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18", "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", "typo3/html-sanitizer": ">=1,<=1.5.2|>=2,<=2.1.3", "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.3.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<3.3.23|>=4,<4.0.17|>=4.1,<4.1.16|>=4.2,<4.2.12|>=4.3,<4.3.3", @@ -17315,33 +19875,38 @@ "ua-parser/uap-php": "<3.8", "uasoft-indonesia/badaso": "<=2.9.7", "unisharp/laravel-filemanager": "<2.9.1", - "unopim/unopim": "<0.1.5", + "universal-omega/dynamic-page-list3": "<3.6.4", + "unopim/unopim": "<=0.3", "userfrosting/userfrosting": ">=0.3.1,<4.6.3", "usmanhalalit/pixie": "<1.0.3|>=2,<2.0.2", "uvdesk/community-skeleton": "<=1.1.1", "uvdesk/core-framework": "<=1.1.1", "vanilla/safecurl": "<0.9.2", "verbb/comments": "<1.5.5", - "verbb/formie": "<2.1.6", + "verbb/formie": "<=2.1.43", "verbb/image-resizer": "<2.0.9", "verbb/knock-knock": "<1.2.8", "verot/class.upload.php": "<=2.1.6", + "vertexvaar/falsftp": "<0.2.6", "villagedefrance/opencart-overclocked": "<=1.11.1", "vova07/yii2-fileapi-widget": "<0.1.9", - "vrana/adminer": "<4.8.1", + "vrana/adminer": "<=4.8.1", "vufind/vufind": ">=2,<9.1.1", "waldhacker/hcaptcha": "<2.1.2", "wallabag/tcpdf": "<6.2.22", - "wallabag/wallabag": "<2.6.7", + "wallabag/wallabag": "<2.6.11", "wanglelecc/laracms": "<=1.0.3", + "wapplersystems/a21glossary": "<=0.4.10", "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", + "web-tp3/wec_map": "<3.0.3", "webbuilders-group/silverstripe-kapost-bridge": "<0.4", "webcoast/deferred-image-processing": "<1.0.2", "webklex/laravel-imap": "<5.3", "webklex/php-imap": "<5.3", "webpa/webpa": "<3.1.2", + "webreinvent/vaahcms": "<=2.3.1", "wikibase/wikibase": "<=1.39.3", "wikimedia/parsoid": "<0.12.2", "willdurand/js-translation-bundle": "<2.1.1", @@ -17362,23 +19927,24 @@ "xataface/xataface": "<3", "xpressengine/xpressengine": "<3.0.15", "yab/quarx": "<2.4.5", - "yeswiki/yeswiki": "<=4.4.4", + "yeswiki/yeswiki": "<=4.5.4", "yetiforce/yetiforce-crm": "<6.5", "yidashi/yii2cmf": "<=2", "yii2mod/yii2-cms": "<1.9.2", - "yiisoft/yii": "<1.1.29", - "yiisoft/yii2": "<2.0.49.4-dev", + "yiisoft/yii": "<1.1.31", + "yiisoft/yii2": "<2.0.52", "yiisoft/yii2-authclient": "<2.2.15", "yiisoft/yii2-bootstrap": "<2.0.4", - "yiisoft/yii2-dev": "<2.0.43", + "yiisoft/yii2-dev": "<=2.0.45", "yiisoft/yii2-elasticsearch": "<2.0.5", "yiisoft/yii2-gii": "<=2.2.4", "yiisoft/yii2-jui": "<2.0.4", - "yiisoft/yii2-redis": "<2.0.8", + "yiisoft/yii2-redis": "<2.0.20", "yikesinc/yikes-inc-easy-mailchimp-extender": "<6.8.6", "yoast-seo-for-typo3/yoast_seo": "<7.2.3", "yourls/yourls": "<=1.8.2", "yuan1994/tpadmin": "<=1.3.12", + "z-push/z-push-dev": "<2.7.6", "zencart/zencart": "<=1.5.7.0-beta", "zendesk/zendesk_api_client_php": "<2.2.11", "zendframework/zend-cache": ">=2.4,<2.4.8|>=2.5,<2.5.3", @@ -17453,32 +20019,32 @@ "type": "tidelift" } ], - "time": "2025-01-06T20:04:58+00:00" + "time": "2025-10-17T18:06:27+00:00" }, { "name": "sebastian/cli-parser", - "version": "1.0.2", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", - "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -17501,7 +20067,8 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" }, "funding": [ { @@ -17509,32 +20076,32 @@ "type": "github" } ], - "time": "2024-03-02T06:27:43+00:00" + "time": "2024-07-03T04:41:36+00:00" }, { "name": "sebastian/code-unit", - "version": "1.0.8", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -17557,7 +20124,8 @@ "homepage": "https://github.com/sebastianbergmann/code-unit", "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3" }, "funding": [ { @@ -17565,32 +20133,32 @@ "type": "github" } ], - "time": "2020-10-26T13:08:54+00:00" + "time": "2025-03-19T07:56:08+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "2.0.3", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + "reference": "183a9b2632194febd219bb9246eee421dad8d45e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -17612,7 +20180,8 @@ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" }, "funding": [ { @@ -17620,34 +20189,39 @@ "type": "github" } ], - "time": "2020-09-28T05:30:19+00:00" + "time": "2024-07-03T04:45:54+00:00" }, { "name": "sebastian/comparator", - "version": "4.0.8", + "version": "6.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/85c77556683e6eee4323e4c5468641ca0237e2e8", + "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/diff": "^4.0", - "sebastian/exporter": "^4.0" + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.4" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "6.3-dev" } }, "autoload": { @@ -17686,41 +20260,54 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.2" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "time": "2022-09-14T12:41:17+00:00" + "time": "2025-08-10T08:07:46+00:00" }, { "name": "sebastian/complexity", - "version": "2.0.3", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", - "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=7.3" + "nikic/php-parser": "^5.0", + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -17743,7 +20330,8 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" }, "funding": [ { @@ -17751,33 +20339,33 @@ "type": "github" } ], - "time": "2023-12-22T06:19:30+00:00" + "time": "2024-07-03T04:49:50+00:00" }, { "name": "sebastian/diff", - "version": "4.0.6", + "version": "6.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", - "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3", + "phpunit/phpunit": "^11.0", "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -17809,7 +20397,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" }, "funding": [ { @@ -17817,27 +20406,27 @@ "type": "github" } ], - "time": "2024-03-02T06:30:58+00:00" + "time": "2024-07-03T04:53:05+00:00" }, { "name": "sebastian/environment", - "version": "5.1.5", + "version": "7.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a5c75038693ad2e8d4b6c15ba2403532647830c4", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.3" }, "suggest": { "ext-posix": "*" @@ -17845,7 +20434,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.1-dev" + "dev-main": "7.2-dev" } }, "autoload": { @@ -17864,7 +20453,7 @@ } ], "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", + "homepage": "https://github.com/sebastianbergmann/environment", "keywords": [ "Xdebug", "environment", @@ -17872,42 +20461,55 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/7.2.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" } ], - "time": "2023-02-03T06:03:51+00:00" + "time": "2025-05-21T11:55:47+00:00" }, { "name": "sebastian/exporter", - "version": "4.0.6", + "version": "6.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/70a298763b40b213ec087c51c739efcaa90bcd74", + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/recursion-context": "^4.0" + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" }, "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "6.3-dev" } }, "autoload": { @@ -17949,46 +20551,56 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.2" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" } ], - "time": "2024-03-02T06:33:00+00:00" + "time": "2025-09-24T06:12:51+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.7", + "version": "7.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" + "reference": "3be331570a721f9a4b5917f4209773de17f747d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", - "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-uopz": "*" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -18007,13 +20619,14 @@ } ], "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", "keywords": [ "global state" ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" }, "funding": [ { @@ -18021,33 +20634,33 @@ "type": "github" } ], - "time": "2024-03-02T06:35:11+00:00" + "time": "2024-07-03T04:57:36+00:00" }, { "name": "sebastian/lines-of-code", - "version": "1.0.4", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", - "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=7.3" + "nikic/php-parser": "^5.0", + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -18070,7 +20683,8 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" }, "funding": [ { @@ -18078,34 +20692,34 @@ "type": "github" } ], - "time": "2023-12-22T06:20:34+00:00" + "time": "2024-07-03T04:58:38+00:00" }, { "name": "sebastian/object-enumerator", - "version": "4.0.4", + "version": "6.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + "reference": "f5b498e631a74204185071eb41f33f38d64608aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -18127,7 +20741,8 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" }, "funding": [ { @@ -18135,32 +20750,32 @@ "type": "github" } ], - "time": "2020-10-26T13:12:34+00:00" + "time": "2024-07-03T05:00:13+00:00" }, { "name": "sebastian/object-reflector", - "version": "2.0.4", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -18182,7 +20797,8 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" }, "funding": [ { @@ -18190,32 +20806,32 @@ "type": "github" } ], - "time": "2020-10-26T13:14:26+00:00" + "time": "2024-07-03T05:01:32+00:00" }, { "name": "sebastian/recursion-context", - "version": "4.0.5", + "version": "6.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -18245,94 +20861,53 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" - } - ], - "time": "2023-02-03T06:07:39+00:00" - }, - { - "name": "sebastian/resource-operations", - "version": "3.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", - "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ + }, { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "support": { - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" - }, - "funding": [ + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2024-03-14T16:00:52+00:00" + "time": "2025-08-13T04:42:22+00:00" }, { "name": "sebastian/type", - "version": "3.2.1", + "version": "5.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -18355,37 +20930,50 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/5.1.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" } ], - "time": "2023-02-03T06:13:03+00:00" + "time": "2025-08-09T06:55:48+00:00" }, { "name": "sebastian/version", - "version": "3.0.2", + "version": "5.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c6c1022351a901512170118436c764e473f6de8c" + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", - "reference": "c6c1022351a901512170118436c764e473f6de8c", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -18408,7 +20996,8 @@ "homepage": "https://github.com/sebastianbergmann/version", "support": { "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" }, "funding": [ { @@ -18416,110 +21005,88 @@ "type": "github" } ], - "time": "2020-09-28T06:39:44+00:00" + "time": "2024-10-09T05:16:32+00:00" }, { - "name": "symfony/browser-kit", - "version": "v6.4.13", + "name": "staabm/side-effects-detector", + "version": "1.0.5", "source": { "type": "git", - "url": "https://github.com/symfony/browser-kit.git", - "reference": "65d4b3fd9556e4b5b41287bef93c671f8f9f86ab" + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/65d4b3fd9556e4b5b41287bef93c671f8f9f86ab", - "reference": "65d4b3fd9556e4b5b41287bef93c671f8f9f86ab", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/dom-crawler": "^5.4|^6.0|^7.0" + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" }, "require-dev": { - "symfony/css-selector": "^5.4|^6.0|^7.0", - "symfony/http-client": "^5.4|^6.0|^7.0", - "symfony/mime": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0" + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" }, "type": "library", "autoload": { - "psr-4": { - "Symfony\\Component\\BrowserKit\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "lib/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" ], - "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.13" + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/staabm", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2024-10-25T15:07:50+00:00" + "time": "2024-10-20T05:08:20+00:00" }, { - "name": "symfony/debug-bundle", - "version": "v6.4.13", + "name": "symfony/browser-kit", + "version": "v7.3.2", "source": { "type": "git", - "url": "https://github.com/symfony/debug-bundle.git", - "reference": "7bcfaff39e094cc09455201916d016d9b2ae08ff" + "url": "https://github.com/symfony/browser-kit.git", + "reference": "f0b889b73a845cddef1d25fe207b37fd04cb5419" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/7bcfaff39e094cc09455201916d016d9b2ae08ff", - "reference": "7bcfaff39e094cc09455201916d016d9b2ae08ff", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/f0b889b73a845cddef1d25fe207b37fd04cb5419", + "reference": "f0b889b73a845cddef1d25fe207b37fd04cb5419", "shasum": "" }, "require": { - "ext-xml": "*", - "php": ">=8.1", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/twig-bridge": "^5.4|^6.0|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" - }, - "conflict": { - "symfony/config": "<5.4", - "symfony/dependency-injection": "<5.4" + "php": ">=8.2", + "symfony/dom-crawler": "^6.4|^7.0" }, "require-dev": { - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/web-profiler-bundle": "^5.4|^6.0|^7.0" + "symfony/css-selector": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0" }, - "type": "symfony-bundle", + "type": "library", "autoload": { "psr-4": { - "Symfony\\Bundle\\DebugBundle\\": "" + "Symfony\\Component\\BrowserKit\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -18539,10 +21106,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides a tight integration of the Symfony VarDumper component and the ServerLogCommand from MonologBridge into the Symfony full-stack framework", + "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/debug-bundle/tree/v6.4.13" + "source": "https://github.com/symfony/browser-kit/tree/v7.3.2" }, "funding": [ { @@ -18553,40 +21120,48 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2025-07-10T08:47:49+00:00" }, { - "name": "symfony/dom-crawler", - "version": "v6.4.16", + "name": "symfony/debug-bundle", + "version": "v7.3.4", "source": { "type": "git", - "url": "https://github.com/symfony/dom-crawler.git", - "reference": "4304e6ad5c894a9c72831ad459f627bfd35d766d" + "url": "https://github.com/symfony/debug-bundle.git", + "reference": "30f922edd53dd85238f1f26dbb68a044109f8f0e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/4304e6ad5c894a9c72831ad459f627bfd35d766d", - "reference": "4304e6ad5c894a9c72831ad459f627bfd35d766d", + "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/30f922edd53dd85238f1f26dbb68a044109f8f0e", + "reference": "30f922edd53dd85238f1f26dbb68a044109f8f0e", "shasum": "" }, "require": { - "masterminds/html5": "^2.6", - "php": ">=8.1", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.0" + "composer-runtime-api": ">=2.1", + "ext-xml": "*", + "php": ">=8.2", + "symfony/config": "^7.3", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/twig-bridge": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" }, "require-dev": { - "symfony/css-selector": "^5.4|^6.0|^7.0" + "symfony/web-profiler-bundle": "^6.4|^7.0" }, - "type": "library", + "type": "symfony-bundle", "autoload": { "psr-4": { - "Symfony\\Component\\DomCrawler\\": "" + "Symfony\\Bundle\\DebugBundle\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -18606,10 +21181,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Eases DOM navigation for HTML and XML documents", + "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/dom-crawler/tree/v6.4.16" + "source": "https://github.com/symfony/debug-bundle/tree/v7.3.4" }, "funding": [ { @@ -18620,30 +21195,34 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-13T15:06:22+00:00" + "time": "2025-09-10T12:00:31+00:00" }, { "name": "symfony/maker-bundle", - "version": "v1.61.0", + "version": "v1.64.0", "source": { "type": "git", "url": "https://github.com/symfony/maker-bundle.git", - "reference": "a3b7f14d349f8f44ed752d4dde2263f77510cc18" + "reference": "c86da84640b0586e92aee2b276ee3638ef2f425a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/a3b7f14d349f8f44ed752d4dde2263f77510cc18", - "reference": "a3b7f14d349f8f44ed752d4dde2263f77510cc18", + "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/c86da84640b0586e92aee2b276ee3638ef2f425a", + "reference": "c86da84640b0586e92aee2b276ee3638ef2f425a", "shasum": "" }, "require": { "doctrine/inflector": "^2.0", - "nikic/php-parser": "^4.18|^5.0", + "nikic/php-parser": "^5.0", "php": ">=8.1", "symfony/config": "^6.4|^7.0", "symfony/console": "^6.4|^7.0", @@ -18666,6 +21245,7 @@ "symfony/http-client": "^6.4|^7.0", "symfony/phpunit-bridge": "^6.4.1|^7.0", "symfony/security-core": "^6.4|^7.0", + "symfony/security-http": "^6.4|^7.0", "symfony/yaml": "^6.4|^7.0", "twig/twig": "^3.0|^4.x-dev" }, @@ -18701,7 +21281,7 @@ ], "support": { "issues": "https://github.com/symfony/maker-bundle/issues", - "source": "https://github.com/symfony/maker-bundle/tree/v1.61.0" + "source": "https://github.com/symfony/maker-bundle/tree/v1.64.0" }, "funding": [ { @@ -18717,31 +21297,31 @@ "type": "tidelift" } ], - "time": "2024-08-29T22:50:23+00:00" + "time": "2025-06-23T16:12:08+00:00" }, { "name": "symfony/phpunit-bridge", - "version": "v6.4.16", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "cebafe2f1ad2d1e745c1015b7c2519592341e4e6" + "reference": "ed77a629c13979e051b7000a317966474d566398" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/cebafe2f1ad2d1e745c1015b7c2519592341e4e6", - "reference": "cebafe2f1ad2d1e745c1015b7c2519592341e4e6", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/ed77a629c13979e051b7000a317966474d566398", + "reference": "ed77a629c13979e051b7000a317966474d566398", "shasum": "" }, "require": { - "php": ">=7.1.3" + "php": ">=7.2.5" }, "conflict": { "phpunit/phpunit": "<7.5|9.1.2" }, "require-dev": { "symfony/deprecation-contracts": "^2.5|^3.0", - "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^5.4|^6.4|^7.0", "symfony/polyfill-php81": "^1.27" }, "bin": [ @@ -18782,8 +21362,11 @@ ], "description": "Provides utilities for PHPUnit, especially user deprecation notices management", "homepage": "https://symfony.com/", + "keywords": [ + "testing" + ], "support": { - "source": "https://github.com/symfony/phpunit-bridge/tree/v6.4.16" + "source": "https://github.com/symfony/phpunit-bridge/tree/v7.3.4" }, "funding": [ { @@ -18794,47 +21377,54 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-13T15:06:22+00:00" + "time": "2025-09-12T12:18:52+00:00" }, { "name": "symfony/web-profiler-bundle", - "version": "v6.4.17", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/web-profiler-bundle.git", - "reference": "979f8ee1a4f2464c20f3fef0d2111827fef2e97e" + "reference": "f305fa4add690bb7d6b14ab61f37c3bd061a3dd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/979f8ee1a4f2464c20f3fef0d2111827fef2e97e", - "reference": "979f8ee1a4f2464c20f3fef0d2111827fef2e97e", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/f305fa4add690bb7d6b14ab61f37c3bd061a3dd7", + "reference": "f305fa4add690bb7d6b14ab61f37c3bd061a3dd7", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/config": "^5.4|^6.0|^7.0", + "composer-runtime-api": ">=2.1", + "php": ">=8.2", + "symfony/config": "^7.3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/framework-bundle": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", - "symfony/routing": "^5.4|^6.0|^7.0", - "symfony/twig-bundle": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" + "symfony/routing": "^6.4|^7.0", + "symfony/twig-bundle": "^6.4|^7.0", + "twig/twig": "^3.12" }, "conflict": { - "symfony/form": "<5.4", - "symfony/mailer": "<5.4", - "symfony/messenger": "<5.4", - "symfony/twig-bundle": ">=7.0" + "symfony/form": "<6.4", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/serializer": "<7.2", + "symfony/workflow": "<7.3" }, "require-dev": { - "symfony/browser-kit": "^5.4|^6.0|^7.0", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/css-selector": "^5.4|^6.0|^7.0", - "symfony/stopwatch": "^5.4|^6.0|^7.0" + "symfony/browser-kit": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0" }, "type": "symfony-bundle", "autoload": { @@ -18865,7 +21455,7 @@ "dev" ], "support": { - "source": "https://github.com/symfony/web-profiler-bundle/tree/v6.4.17" + "source": "https://github.com/symfony/web-profiler-bundle/tree/v7.3.4" }, "funding": [ { @@ -18877,72 +21467,15 @@ "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-12-08T23:00:41+00:00" - }, - { - "name": "symplify/easy-coding-standard", - "version": "12.5.5", - "source": { - "type": "git", - "url": "https://github.com/easy-coding-standard/easy-coding-standard.git", - "reference": "16a6ac7f452e230fdcc81f1b35b2366903fcecf3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/16a6ac7f452e230fdcc81f1b35b2366903fcecf3", - "reference": "16a6ac7f452e230fdcc81f1b35b2366903fcecf3", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "conflict": { - "friendsofphp/php-cs-fixer": "<3.46", - "phpcsstandards/php_codesniffer": "<3.8", - "symplify/coding-standard": "<12.1" - }, - "suggest": { - "ext-dom": "Needed to support checkstyle output format in class CheckstyleOutputFormatter" - }, - "bin": [ - "bin/ecs" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Use Coding Standard with 0-knowledge of PHP-CS-Fixer and PHP_CodeSniffer", - "keywords": [ - "Code style", - "automation", - "fixer", - "static analysis" - ], - "support": { - "issues": "https://github.com/easy-coding-standard/easy-coding-standard/issues", - "source": "https://github.com/easy-coding-standard/easy-coding-standard/tree/12.5.5" - }, - "funding": [ - { - "url": "https://www.paypal.me/rectorphp", - "type": "custom" + "url": "https://github.com/nicolas-grekas", + "type": "github" }, { - "url": "https://github.com/tomasvotruba", - "type": "github" + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2025-01-02T08:43:03+00:00" + "time": "2025-09-25T08:03:55+00:00" }, { "name": "theseer/tokenizer", @@ -18995,23 +21528,15 @@ "time": "2024-03-03T12:36:25+00:00" } ], - "aliases": [ - { - "package": "brick/math", - "version": "0.12.1.0", - "alias": "0.11.0", - "alias_normalized": "0.11.0.0" - } - ], + "aliases": [], "minimum-stability": "stable", "stability-flags": { - "florianv/swap-bundle": 20, "roave/security-advisories": 20 }, "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^8.1", + "php": "^8.2", "ext-ctype": "*", "ext-dom": "*", "ext-gd": "*", @@ -19020,9 +21545,9 @@ "ext-json": "*", "ext-mbstring": "*" }, - "platform-dev": [], + "platform-dev": {}, "platform-overrides": { - "php": "8.1.0" + "php": "8.2.0" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/config/banner.md b/config/banner.md index 997ca15e7..1d38a3f3d 100644 --- a/config/banner.md +++ b/config/banner.md @@ -1,14 +1,4 @@ -Welcome to Part-DB. - -If you want to change this banner, edit `config/banner.md` file or set the `BANNER` environment variable. +**Attention**: +Since Version 2.0.0 this file is no longer used. -
-

-And God said
-$\nabla \cdot \vec{D} = \rho$, -$\nabla \cdot \vec{B} = 0$, -$\nabla \times \vec{E} = -\frac{\partial \vec{B}}{\partial t}$, -$\nabla \times \vec{H} = \vec{j} + \frac{\partial \vec{D}}{\partial t}$,
-and then there was light. -

-
\ No newline at end of file +You can now set the banner text directly in the admin interface, or by setting the `BANNER` environment variable. diff --git a/config/bundles.php b/config/bundles.php index ea0660844..ae7dc9ccb 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -30,6 +30,7 @@ Jbtronics\DompdfFontLoaderBundle\DompdfFontLoaderBundle::class => ['all' => true], KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle::class => ['all' => true], Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true], - ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true], + Jbtronics\SettingsBundle\JbtronicsSettingsBundle::class => ['all' => true], Jbtronics\TranslationEditorBundle\JbtronicsTranslationEditorBundle::class => ['dev' => true], + ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true], ]; diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml index b32ddac7b..1b679cd12 100644 --- a/config/packages/api_platform.yaml +++ b/config/packages/api_platform.yaml @@ -32,6 +32,9 @@ api_platform: pagination_client_items_per_page: true # Allow clients to override the default items per page - keep_legacy_inflector: false # Need to be true, or some tests will fail - use_symfony_listeners: true \ No newline at end of file + use_symfony_listeners: true + + serializer: + # Change this to false later, to remove the hydra prefix on the API + hydra_prefix: true diff --git a/config/packages/csrf.yaml b/config/packages/csrf.yaml new file mode 100644 index 000000000..01db62670 --- /dev/null +++ b/config/packages/csrf.yaml @@ -0,0 +1,12 @@ +# Enable stateless CSRF protection for forms and logins/logouts +framework: + form: + csrf_protection: + token_id: submit + + csrf_protection: + check_header: true + stateless_token_ids: + - submit + - authenticate + - logout diff --git a/config/packages/datatables.yaml b/config/packages/datatables.yaml index 63d386ee5..f1ea4715a 100644 --- a/config/packages/datatables.yaml +++ b/config/packages/datatables.yaml @@ -8,8 +8,9 @@ datatables: # Set options, as documented at https://datatables.net/reference/option/ 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 + lengthMenu : [[10, 25, 50, 100], [10, 25, 50, 100]] # We add the "All" option, when part tables are generated + #pageLength: '%partdb.table.default_page_size%' # Set to -1 to disable pagination (i.e. show all rows) by default + pageLength: 50 #TODO dom: " <'row' <'col mb-2 input-group flex-nowrap' B l > <'col-auto mb-2' < p >>> <'card' rt @@ -17,7 +18,7 @@ datatables: > <'row' <'col mt-2 input-group flex-nowrap' B l > <'col-auto mt-2' < p >>>" pagingType: 'simple_numbers' - searching: true + searching: false stateSave: true diff --git a/config/packages/doctrine.php b/config/packages/doctrine.php new file mode 100644 index 000000000..47584ed75 --- /dev/null +++ b/config/packages/doctrine.php @@ -0,0 +1,33 @@ +. + */ + +declare(strict_types=1); + +/** + * This class extends the default doctrine ORM configuration to enable native lazy objects on PHP 8.4+. + * We have to do this in a PHP file, because the yaml file does not support conditionals on PHP version. + */ + +return static function(\Symfony\Config\DoctrineConfig $doctrine) { + //On PHP 8.4+ we can use native lazy objects, which are much more efficient than proxies. + if (PHP_VERSION_ID >= 80400) { + $doctrine->orm()->enableNativeLazyObjects(true); + } +}; diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index 3211fbbe1..5261c2957 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -25,10 +25,6 @@ doctrine: tinyint: class: App\Doctrine\Types\TinyIntType - # This was removed in doctrine/orm 4.0 but we need it for the WebauthnKey entity - array: - class: App\Doctrine\Types\ArrayType - schema_filter: ~^(?!internal)~ # Only enable this when needed profiling_collect_backtrace: false @@ -39,6 +35,8 @@ doctrine: report_fields_where_declared: true validate_xml_mapping: true naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware + identity_generation_preferences: + Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity auto_mapping: true controller_resolver: auto_mapping: true diff --git a/config/packages/framework.yaml b/config/packages/framework.yaml index 279c51f5a..6843a1774 100644 --- a/config/packages/framework.yaml +++ b/config/packages/framework.yaml @@ -1,9 +1,6 @@ # see https://symfony.com/doc/current/reference/configuration/framework.html framework: secret: '%env(APP_SECRET)%' - csrf_protection: true - annotations: false - handle_all_throwables: true # We set this header by ourselves, so we can disable it here disallow_search_engine_index: false @@ -30,8 +27,11 @@ framework: #esi: true #fragments: true - php_errors: - log: true + + + form: { csrf_protection: { token_id: 'submit' } } + csrf_protection: + stateless_token_ids: ['submit', 'authenticate', 'logout'] when@test: framework: diff --git a/config/packages/knpu_oauth2_client.yaml b/config/packages/knpu_oauth2_client.yaml index 7d296a8be..5e56d5c55 100644 --- a/config/packages/knpu_oauth2_client.yaml +++ b/config/packages/knpu_oauth2_client.yaml @@ -6,8 +6,8 @@ knpu_oauth2_client: type: generic provider_class: '\League\OAuth2\Client\Provider\GenericProvider' - client_id: '%env(PROVIDER_DIGIKEY_CLIENT_ID)%' - client_secret: '%env(PROVIDER_DIGIKEY_SECRET)%' + client_id: '%env(settings:digikey:clientId)%' + client_secret: '%env(settings:digikey:secret)%' redirect_route: 'oauth_client_check' redirect_params: {name: 'ip_digikey_oauth'} @@ -26,8 +26,8 @@ knpu_oauth2_client: type: generic provider_class: '\League\OAuth2\Client\Provider\GenericProvider' - client_id: '%env(PROVIDER_OCTOPART_CLIENT_ID)%' - client_secret: '%env(PROVIDER_OCTOPART_SECRET)%' + client_id: '%env(settings:octopart:clientId)%' + client_secret: '%env(settings:octopart:secret)%' redirect_route: 'oauth_client_check' redirect_params: { name: 'ip_octopart_oauth' } diff --git a/config/packages/monolog.yaml b/config/packages/monolog.yaml index 44a078b85..725ebd7c8 100644 --- a/config/packages/monolog.yaml +++ b/config/packages/monolog.yaml @@ -69,6 +69,7 @@ when@docker: excluded_http_codes: [404, 405] buffer_size: 50 # How many messages should be saved? Prevent memory leaks include_stacktraces: true + channels: ["!deprecation"] nested: type: stream path: "php://stderr" diff --git a/config/packages/nelmio_security.yaml b/config/packages/nelmio_security.yaml index 1cb74da7f..6b2b73374 100644 --- a/config/packages/nelmio_security.yaml +++ b/config/packages/nelmio_security.yaml @@ -20,12 +20,6 @@ nelmio_security: - 'digikey.com' - 'nexar.com' - # forces Microsoft's XSS-Protection with - # its block mode - xss_protection: - enabled: true - mode_block: true - # Send a full URL in the `Referer` header when performing a same-origin request, # only send the origin of the document to secure destination (HTTPS->HTTPS), # and send no header to a less secure destination (HTTPS->HTTP). @@ -69,9 +63,3 @@ nelmio_security: - 'data:' block-all-mixed-content: true # defaults to false, blocks HTTP content over HTTPS transport # upgrade-insecure-requests: true # defaults to false, upgrades HTTP requests to HTTPS transport - -when@dev: - # disables the Content-Security-Policy header - nelmio_security: - csp: - enabled: false \ No newline at end of file diff --git a/config/packages/property_info.yaml b/config/packages/property_info.yaml new file mode 100644 index 000000000..dd31b9da2 --- /dev/null +++ b/config/packages/property_info.yaml @@ -0,0 +1,3 @@ +framework: + property_info: + with_constructor_extractor: true diff --git a/config/packages/routing.yaml b/config/packages/routing.yaml index df5d98d2e..0f34f872c 100644 --- a/config/packages/routing.yaml +++ b/config/packages/routing.yaml @@ -1,7 +1,5 @@ framework: router: - utf8: true - # Configure how to generate URLs in non-HTTP contexts, such as CLI commands. # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands default_uri: '%env(DEFAULT_URI)%' diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 95f5c6b17..e7a44e0ce 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -13,7 +13,7 @@ security: firewalls: dev: - pattern: ^/(_(profiler|wdt)|css|images|js)/ + pattern: ^/(_(profiler|wdt)|css|images|js|\.well-known)/ security: false main: provider: app_user_provider diff --git a/config/packages/settings.yaml b/config/packages/settings.yaml new file mode 100644 index 000000000..c16d18049 --- /dev/null +++ b/config/packages/settings.yaml @@ -0,0 +1,15 @@ +jbtronics_settings: + default_storage_adapter: Jbtronics\SettingsBundle\Storage\ORMStorageAdapter + + cache: + default_cacheable: true + + orm_storage: + default_entity_class: App\Entity\SettingsEntry + + +# Disable caching for development environment +when@dev: + jbtronics_settings: + cache: + default_cacheable: false diff --git a/config/packages/swap.yaml b/config/packages/swap.yaml index 2767f740c..4ef8fbdf5 100644 --- a/config/packages/swap.yaml +++ b/config/packages/swap.yaml @@ -5,6 +5,12 @@ florianv_swap: providers: european_central_bank: ~ # European Central Bank (only works for EUR base currency) - fixer: # Fixer.io (needs an API key) - access_key: "%env(FIXER_API_KEY)%" - #exchange_rates_api: ~ \ No newline at end of file + central_bank_of_czech_republic: ~ + central_bank_of_republic_turkey: ~ + national_bank_of_romania: ~ + + fixer: # Fixer.io (needs an API key) + access_key: "%env(string:settings:exchange_rate:fixerApiKey)%" + + frankfurter: ~ + fawazahmed_currency_api: ~ diff --git a/config/packages/translation.yaml b/config/packages/translation.yaml index 7266a176f..cbc1cd7e5 100644 --- a/config/packages/translation.yaml +++ b/config/packages/translation.yaml @@ -1,11 +1,10 @@ framework: - default_locale: '%partdb.locale%' + default_locale: 'en' # Just enable the locales we need for performance reasons. enabled_locale: '%partdb.locale_menu%' translator: default_path: '%kernel.project_dir%/translations' fallbacks: - - '%partdb.locale%' - 'en' providers: # crowdin: diff --git a/config/packages/twig.yaml b/config/packages/twig.yaml index 5b2d64e56..674aa3177 100644 --- a/config/packages/twig.yaml +++ b/config/packages/twig.yaml @@ -6,16 +6,12 @@ twig: '%kernel.project_dir%/assets/css': css globals: - partdb_title: '%partdb.title%' - default_currency: '%partdb.default_currency%' - global_theme: '%partdb.global_theme%' allow_email_pw_reset: '%partdb.users.email_pw_reset%' locale_menu: '%partdb.locale_menu%' attachment_manager: '@App\Services\Attachments\AttachmentManager' label_profile_dropdown_helper: '@App\Services\LabelSystem\LabelProfileDropdownHelper' error_page_admin_email: '%partdb.error_pages.admin_email%' error_page_show_help: '%partdb.error_pages.show_help%' - sidebar_items: '%partdb.sidebar.items%' sidebar_tree_updater: '@App\Services\Trees\SidebarTreeUpdater' avatar_helper: '@App\Services\UserSystem\UserAvatarHelper' available_themes: '%partdb.available_themes%' diff --git a/config/packages/uid.yaml b/config/packages/uid.yaml deleted file mode 100644 index 01520944f..000000000 --- a/config/packages/uid.yaml +++ /dev/null @@ -1,4 +0,0 @@ -framework: - uid: - default_uuid_version: 7 - time_based_uuid_version: 7 diff --git a/config/packages/ux_turbo.yaml b/config/packages/ux_turbo.yaml new file mode 100644 index 000000000..c2a6a44e7 --- /dev/null +++ b/config/packages/ux_turbo.yaml @@ -0,0 +1,4 @@ +# Enable stateless CSRF protection for forms and logins/logouts +framework: + csrf_protection: + check_header: true diff --git a/config/packages/validator.yaml b/config/packages/validator.yaml index 0201281d3..dd47a6ad8 100644 --- a/config/packages/validator.yaml +++ b/config/packages/validator.yaml @@ -1,7 +1,5 @@ framework: validation: - email_validation_mode: html5 - # Enables validator auto-mapping support. # For instance, basic validation constraints will be inferred from Doctrine's metadata. #auto_mapping: diff --git a/config/packages/web_profiler.yaml b/config/packages/web_profiler.yaml index b94611102..15112444b 100644 --- a/config/packages/web_profiler.yaml +++ b/config/packages/web_profiler.yaml @@ -1,17 +1,14 @@ when@dev: web_profiler: - toolbar: true - intercept_redirects: false + toolbar: + ajax_replace: true framework: profiler: - only_exceptions: false collect_serializer_data: true when@test: - web_profiler: - toolbar: false - intercept_redirects: false - framework: - profiler: { collect: false } + profiler: + collect: false + collect_serializer_data: true diff --git a/config/parameters.yaml b/config/parameters.yaml index b2c10893a..d4fe7581b 100644 --- a/config/parameters.yaml +++ b/config/parameters.yaml @@ -5,14 +5,10 @@ parameters: ###################################################################################################################### # Common ###################################################################################################################### - partdb.locale: '%env(string:DEFAULT_LANG)%' # The default language to use serverwide - partdb.timezone: '%env(string:DEFAULT_TIMEZONE)%' # The default timezone - partdb.title: '%env(trim:string:INSTANCE_NAME)%' # The title shown inside of Part-DB (e.g. in the navbar and on homepage) - 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', '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. + + # This is used as workaround for places where we can not access the settings directly (like the 2FA application names) + partdb.title: '%env(string:settings:customization:instanceName)%' # The title shown inside of Part-DB (e.g. in the navbar and on homepage) + partdb.locale_menu: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh', 'pl', 'hu'] # The languages that are shown in user drop down menu partdb.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 @@ -22,11 +18,8 @@ parameters: # Users and Privacy ###################################################################################################################### partdb.gdpr_compliance: true # If this option is activated, IP addresses are anonymized to be GDPR compliant - partdb.users.use_gravatar: '%env(bool:USE_GRAVATAR)%' # Set to false, if no Gravatar images should be used for user profiles. partdb.users.email_pw_reset: '%env(bool:ALLOW_EMAIL_PW_RESET)%' # Config if users are able, to reset their password by email. By default this enabled, when a mail server is configured. - partdb.check_for_updates: '%env(bool:CHECK_FOR_UPDATES)' # Set to false, if Part-DB should not contact the GitHub API to check for updates - ###################################################################################################################### # Mail settings ###################################################################################################################### @@ -36,11 +29,8 @@ parameters: ###################################################################################################################### # Attachments and files ###################################################################################################################### - partdb.attachments.allow_downloads: '%env(bool:ALLOW_ATTACHMENT_DOWNLOADS)%' # Allow users to download attachments to server. Warning: This can be dangerous, because via that feature attackers maybe can access ressources on your intranet! - partdb.attachments.download_by_default: '%env(bool:ATTACHMENT_DOWNLOAD_BY_DEFAULT)%' # If this is set the 'download external files' checkbox is set by default for new attachments (only if allow_downloads is set to true) partdb.attachments.dir.media: 'public/media/' # The folder where uploaded attachment files are saved (must be in public folder) partdb.attachments.dir.secure: 'uploads/' # The folder where secured attachment files are saved (must not be in public/) - partdb.attachments.max_file_size: '%env(string:MAX_ATTACHMENT_FILE_SIZE)%' # The maximum size of an attachment file (in bytes, you can use M for megabytes and G for gigabytes) ###################################################################################################################### # Error pages @@ -53,22 +43,6 @@ parameters: ###################################################################################################################### partdb.saml.enabled: '%env(bool:SAML_ENABLED)%' # If this is set to true, SAML authentication is enabled - ###################################################################################################################### - # Table settings - ###################################################################################################################### - partdb.table.default_page_size: '%env(int:TABLE_DEFAULT_PAGE_SIZE)%' # The default number of entries shown per page in tables - partdb.table.parts.default_columns: '%env(trim:string:TABLE_PARTS_DEFAULT_COLUMNS)%' # The default columns in part tables and their order - - ###################################################################################################################### - # Sidebar - ###################################################################################################################### - # You can configures the default shown tree items in the sidebar here. You can add or remove entries here, to change the number of trees in the sidebar. The possible entries are: categories, locations, footprints, manufacturers, suppliers, devices, tools - partdb.sidebar.items: - - categories - - devices - - tools - partdb.sidebar.root_expanded: true # If this is set to true, the root node of the sidebar is expanded by default - partdb.sidebar.root_node_enable: true # Put all entities below a root node in the sidebar ###################################################################################################################### # Miscellaneous @@ -110,30 +84,18 @@ parameters: # Env default values ###################################################################################################################### - env(DEFAULT_LANG): 'en' - env(DEFAULT_TIMEZONE): 'Europe/Berlin' - env(INSTANCE_NAME): 'Part-DB' - env(BASE_CURRENCY): 'EUR' - env(USE_GRAVATAR): '0' - env(MAX_ATTACHMENT_FILE_SIZE): '100M' - env(REDIRECT_TO_HTTPS): 0 - env(ENFORCE_CHANGE_COMMENTS_FOR): '' - env(ERROR_PAGE_ADMIN_EMAIL): '' env(ERROR_PAGE_SHOW_HELP): 1 env(DEMO_MODE): 0 - env(BANNER): '' env(EMAIL_SENDER_EMAIL): 'noreply@partdb.changeme' env(EMAIL_SENDER_NAME): 'Part-DB Mailer' env(ALLOW_EMAIL_PW_RESET): 0 - env(TABLE_DEFAULT_PAGE_SIZE): 50 - env(TRUSTED_PROXIES): '127.0.0.1' #By default trust only our own server env(TRUSTED_HOSTS): '' # Trust all host names by default @@ -141,11 +103,10 @@ parameters: env(SAML_ROLE_MAPPING): '{}' - env(HISTORY_SAVE_CHANGED_DATA): 1 - env(HISTORY_SAVE_CHANGED_FIELDS): 1 - env(HISTORY_SAVE_REMOVED_DATA): 1 - env(HISTORY_SAVE_NEW_DATA): 1 - - env(EDA_KICAD_CATEGORY_DEPTH): 0 - env(DATABASE_EMULATE_NATURAL_SORT): 0 + + ###################################################################################################################### + # Bulk Info Provider Import Configuration + ###################################################################################################################### + partdb.bulk_import.batch_size: 20 # Number of parts to process in each batch during bulk operations + partdb.bulk_import.max_parts_per_operation: 1000 # Maximum number of parts allowed per bulk import operation diff --git a/config/permissions.yaml b/config/permissions.yaml index b89705562..7acee7f0e 100644 --- a/config/permissions.yaml +++ b/config/permissions.yaml @@ -24,7 +24,7 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co label: "perm.read" # If a part can be read by a user, he can also see all the datastructures (except devices) alsoSet: ['storelocations.read', 'footprints.read', 'categories.read', 'suppliers.read', 'manufacturers.read', - 'currencies.read', 'attachment_types.read', 'measurement_units.read'] + 'currencies.read', 'attachment_types.read', 'measurement_units.read', 'part_custom_states.read'] apiTokenRole: ROLE_API_READ_ONLY edit: label: "perm.edit" @@ -133,6 +133,10 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co <<: *PART_CONTAINING label: "perm.measurement_units" + part_custom_states: + <<: *PART_CONTAINING + label: "perm.part_custom_states" + tools: label: "perm.part.tools" operations: @@ -265,17 +269,13 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co # label: "perm.database.write_db_settings" # alsoSet: ['read_db_settings', 'see_status'] - #config: - # label: "perm.config" - # group: "system" - # operations: - # read_config: - # label: "perm.config.read_config" - # edit_config: - # label: "perm.config.edit_config" - # alsoSet: 'read_config' - # server_info: - # label: "perm.config.server_info" + config: + label: "perm.config" + group: "system" + operations: + change_system_settings: + label: "perm.config.change_system_settings" + apiTokenRole: ROLE_API_ADMIN system: label: "perm.system" @@ -363,6 +363,10 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co label: "perm.revert_elements" alsoSet: ['read_profiles', 'edit_profiles', 'create_profiles', 'delete_profiles'] apiTokenRole: ROLE_API_EDIT + import: + label: "perm.import" + alsoSet: ['read_profiles', 'edit_profiles', 'create_profiles' ] + apiTokenRole: ROLE_API_EDIT api: label: "perm.api" diff --git a/config/routes/framework.yaml b/config/routes/framework.yaml index 0fc74bbac..bc1feace1 100644 --- a/config/routes/framework.yaml +++ b/config/routes/framework.yaml @@ -1,4 +1,4 @@ when@dev: _errors: - resource: '@FrameworkBundle/Resources/config/routing/errors.xml' + resource: '@FrameworkBundle/Resources/config/routing/errors.php' prefix: /_error diff --git a/config/routes/web_profiler.yaml b/config/routes/web_profiler.yaml index 8d85319fd..b3b7b4b0e 100644 --- a/config/routes/web_profiler.yaml +++ b/config/routes/web_profiler.yaml @@ -1,8 +1,8 @@ when@dev: web_profiler_wdt: - resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml' + resource: '@WebProfilerBundle/Resources/config/routing/wdt.php' prefix: /_wdt web_profiler_profiler: - resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml' + resource: '@WebProfilerBundle/Resources/config/routing/profiler.php' prefix: /_profiler diff --git a/config/services.yaml b/config/services.yaml index b2342edd1..17611ceab 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -17,8 +17,6 @@ services: bool $gdpr_compliance: '%partdb.gdpr_compliance%' bool $kernel_debug_enabled: '%kernel.debug%' string $kernel_cache_dir: '%kernel.cache_dir%' - string $partdb_title: '%partdb.title%' - string $base_currency: '%partdb.default_currency%' _instanceof: App\Services\LabelSystem\PlaceholderProviders\PlaceholderProviderInterface: @@ -31,10 +29,6 @@ services: # this creates a service per class whose id is the fully-qualified class name App\: resource: '../src/' - exclude: - - '../src/DependencyInjection/' - - '../src/Entity/' - - '../src/Kernel.php' # controllers are imported separately to make sure services can be injected # as action arguments even if you don't extend any base controller class @@ -76,28 +70,10 @@ services: # Only the event classes specified here are saved to DB (set to []) to log all events $whitelist: [] - 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)%' - - App\Form\AttachmentFormType: - arguments: - $allow_attachments_download: '%partdb.attachments.allow_downloads%' - $max_file_size: '%partdb.attachments.max_file_size%' - $download_by_default: '%partdb.attachments.download_by_default%' - App\Services\Attachments\AttachmentSubmitHandler: arguments: - $allow_attachments_downloads: '%partdb.attachments.allow_downloads%' $mimeTypes: '@mime_types' - $max_upload_size: '%partdb.attachments.max_file_size%' - App\Services\LogSystem\EventCommentNeededHelper: - arguments: - $enforce_change_comments_for: '%partdb.enforce_change_comments_for%' #################################################################################################################### # Attachment system @@ -156,29 +132,6 @@ services: tags: - { name: doctrine.orm.entity_listener } - #################################################################################################################### - # Price system - #################################################################################################################### - App\Command\Currencies\UpdateExchangeRatesCommand: - arguments: - $base_current: '%partdb.default_currency%' - - App\Form\Type\CurrencyEntityType: - arguments: - $base_currency: '%partdb.default_currency%' - - App\Services\Parts\PricedetailHelper: - arguments: - $base_currency: '%partdb.default_currency%' - - App\Services\Formatters\MoneyFormatter: - arguments: - $base_currency: '%partdb.default_currency%' - - App\Services\Tools\ExchangeRateUpdater: - arguments: - $base_currency: '%partdb.default_currency%' - ################################################################################################################### # User system #################################################################################################################### @@ -186,10 +139,6 @@ services: arguments: $demo_mode: '%partdb.demo_mode%' - App\EventSubscriber\UserSystem\SetUserTimezoneSubscriber: - arguments: - $default_timezone: '%partdb.timezone%' - App\Controller\SecurityController: arguments: $allow_email_pw_reset: '%partdb.users.email_pw_reset%' @@ -203,10 +152,6 @@ services: tags: - { name: 'translation.extractor', alias: 'permissionExtractor'} - App\Services\UserSystem\UserAvatarHelper: - arguments: - $use_gravatar: '%partdb.users.use_gravatar%' - App\Form\Type\ThemeChoiceType: arguments: $available_themes: '%partdb.available_themes%' @@ -222,9 +167,6 @@ services: #################################################################################################################### # Table settings #################################################################################################################### - App\DataTables\PartsDataTable: - arguments: - $visible_columns: '%partdb.table.parts.default_columns%' App\DataTables\Helpers\ColumnSortHelper: shared: false # Service has a state so not share it between different tables @@ -246,14 +188,6 @@ services: $fontDirectory: '%kernel.project_dir%/var/dompdf/fonts/' $tmpDirectory: '%kernel.project_dir%/var/dompdf/tmp/' - #################################################################################################################### - # Trees - #################################################################################################################### - App\Services\Trees\TreeViewGenerator: - arguments: - $rootNodeExpandedByDefault: '%partdb.sidebar.root_expanded%' - $rootNodeEnabled: '%partdb.sidebar.root_node_enable%' - #################################################################################################################### # Part info provider system #################################################################################################################### @@ -261,76 +195,12 @@ services: arguments: $providers: !tagged_iterator 'app.info_provider' - App\Services\InfoProviderSystem\Providers\Element14Provider: - arguments: - $api_key: '%env(string:PROVIDER_ELEMENT14_KEY)%' - $store_id: '%env(string:PROVIDER_ELEMENT14_STORE_ID)%' - - App\Services\InfoProviderSystem\Providers\DigikeyProvider: - arguments: - $clientId: '%env(string:PROVIDER_DIGIKEY_CLIENT_ID)%' - $currency: '%env(string:PROVIDER_DIGIKEY_CURRENCY)%' - $language: '%env(string:PROVIDER_DIGIKEY_LANGUAGE)%' - $country: '%env(string:PROVIDER_DIGIKEY_COUNTRY)%' - - App\Services\InfoProviderSystem\Providers\TMEClient: - arguments: - $secret: '%env(string:PROVIDER_TME_SECRET)%' - $token: '%env(string:PROVIDER_TME_KEY)%' - - App\Services\InfoProviderSystem\Providers\TMEProvider: - arguments: - $currency: '%env(string:PROVIDER_TME_CURRENCY)%' - $country: '%env(string:PROVIDER_TME_COUNTRY)%' - $language: '%env(string:PROVIDER_TME_LANGUAGE)%' - $get_gross_prices: '%env(bool:PROVIDER_TME_GET_GROSS_PRICES)%' - - App\Services\InfoProviderSystem\Providers\OctopartProvider: - arguments: - $clientId: '&env(string:PROVIDER_OCTOPART_CLIENT_ID)%' - $secret: '%env(string:PROVIDER_OCTOPART_SECRET)%' - $country: '%env(string:PROVIDER_OCTOPART_COUNTRY)%' - $currency: '%env(string:PROVIDER_OCTOPART_CURRENCY)%' - $search_limit: '%env(int:PROVIDER_OCTOPART_SEARCH_LIMIT)%' - $onlyAuthorizedSellers: '%env(bool:PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS)%' - - App\Services\InfoProviderSystem\Providers\MouserProvider: - arguments: - $api_key: '%env(string:PROVIDER_MOUSER_KEY)%' - $language: '%env(string:PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE)%' - $options: '%env(string:PROVIDER_MOUSER_SEARCH_OPTION)%' - $search_limit: '%env(int:PROVIDER_MOUSER_SEARCH_LIMIT)%' - - App\Services\InfoProviderSystem\Providers\LCSCProvider: - arguments: - $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 #################################################################################################################### App\State\PartDBInfoProvider: arguments: $default_uri: '%partdb.default_uri%' - $global_locale: '%partdb.locale%' - $global_timezone: '%partdb.timezone%' - - #################################################################################################################### - # EDA system - #################################################################################################################### - App\Services\EDA\KiCadHelper: - arguments: - $category_depth: '%env(int:EDA_KICAD_CATEGORY_DEPTH)%' #################################################################################################################### # Symfony overrides @@ -355,7 +225,6 @@ services: #################################################################################################################### App\Controller\RedirectController: arguments: - $default_locale: '%partdb.locale%' $enforce_index_php: '%env(bool:NO_URL_REWRITE_AVAILABLE)%' App\Doctrine\Purger\ResetAutoIncrementPurgerFactory: @@ -370,14 +239,6 @@ services: arguments: $project_dir: '%kernel.project_dir%' - App\Services\System\UpdateAvailableManager: - arguments: - $check_for_updates: '%partdb.check_for_updates%' - - App\Services\System\BannerHelper: - arguments: - $partdb_banner: '%partdb.banner%' - $project_dir: '%kernel.project_dir%' App\Doctrine\Middleware\MySQLSSLConnectionMiddlewareWrapper: arguments: diff --git a/docs/assets/getting_started/system_settings.png b/docs/assets/getting_started/system_settings.png new file mode 100644 index 000000000..5a7d7380a Binary files /dev/null and b/docs/assets/getting_started/system_settings.png differ diff --git a/docs/assets/usage/import_export/part_import_example.csv b/docs/assets/usage/import_export/part_import_example.csv index 087014260..14d4500f9 100644 --- a/docs/assets/usage/import_export/part_import_example.csv +++ b/docs/assets/usage/import_export/part_import_example.csv @@ -1,4 +1,7 @@ -name;description;category;notes;footprint;tags;quantity;storage_location;mass;ipn;mpn;manufacturing_status;manufacturer;supplier;spn;price;favorite;needs_review;minamount;partUnit;manufacturing_status -BC547;NPN transistor;Transistors -> NPN;very important notes;TO -> TO-92;NPN,Transistor;5;Room 1 -> Shelf 1 -> Box 2;10;;;Manufacturer;;You need to fill this line, to use spn and price;BC547C;2,3;0;;;; -BC557;PNP transistor;HTML;;TO -> TO-92;PNP,Transistor;10;Room 2-> Box 3;;Internal1234;;;;;;;;1;;;active -Copper Wire;;Wire;;;;;;;;;;;;;;;;;Meter; \ No newline at end of file +name;description;category;notes;footprint;tags;quantity;storage_location;mass;ipn;mpn;manufacturing_status;manufacturer;supplier;spn;price;favorite;needs_review;minamount;partUnit;eda_info.reference_prefix;eda_info.value;eda_info.visibility;eda_info.exclude_from_bom;eda_info.exclude_from_board;eda_info.exclude_from_sim;eda_info.kicad_symbol;eda_info.kicad_footprint +"MLCC; 0603; 0.22uF";Multilayer ceramic capacitor;Electrical Components->Passive Components->Capacitors_SMD;High quality MLCC;0603;Capacitor,SMD,MLCC,0603;500;Room 1->Shelf 1->Box 2;0.1;CL10B224KO8NNNC;CL10B224KO8NNNC;active;Samsung;LCSC;C160828;0.0023;0;0;1;pcs;C;0.22uF;1;0;0;0;Device:C;Capacitor_SMD:C_0603_1608Metric +"MLCC; 0402; 10pF";Small MLCC for high frequency;Electrical Components->Passive Components->Capacitors_SMD;;0402;Capacitor,SMD,MLCC,0402;500;Room 1->Shelf 1->Box 3;0.05;FCC0402N100J500AT;FCC0402N100J500AT;active;Fenghua;LCSC;C5137557;0.0015;0;0;1;pcs;C;10pF;1;0;0;0;Device:C;Capacitor_SMD:C_0402_1005Metric +"Diode; 1N4148W";Fast switching diode;Electrical Components->Semiconductors->Diodes;Fast recovery time;Diode_SMD:D_SOD-123;Diode,SMD,Schottky;100;Room 2->Box 1;0.2;1N4148W;1N4148W;active;Vishay;LCSC;C917030;0.008;0;0;1;pcs;D;1N4148W;1;0;0;0;Device:D;Diode_SMD:D_SOD-123 +BC547;NPN transistor;Transistors->NPN;very important notes;TO->TO-92;NPN,Transistor;5;Room 1->Shelf 1->Box 2;10;BC547;BC547;active;Generic;LCSC;BC547C;2.3;0;0;1;pcs;Q;BC547;1;0;0;0;Device:Q_NPN_EBC;TO_SOT_Packages_SMD:TO-92_HandSolder +BC557;PNP transistor;Transistors->PNP;PNP complement to BC547;TO->TO-92;PNP,Transistor;10;Room 2->Box 3;10;BC557;BC557;active;Generic;LCSC;BC557C;2.1;0;0;1;pcs;Q;BC557;1;0;0;0;Device:Q_PNP_EBC;TO_SOT_Packages_SMD:TO-92_HandSolder +Copper Wire;Bare copper wire;Wire->Copper;For prototyping;Wire;Wire,Copper;50;Room 3->Spool Rack;0.5;CW-22AWG;CW-22AWG;active;Generic;Local Supplier;LS-CW-22;0.15;0;0;1;Meter;W;22AWG;1;0;0;0;Device:Wire;Connector_PinHeader_2.54mm:PinHeader_1x01_P2.54mm_Vertical diff --git a/docs/configuration.md b/docs/configuration.md index 0ad30a00f..2ba5ce903 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -10,7 +10,7 @@ Part-DBs behavior can be configured to your needs. There are different kinds of user-changeable (changeable dynamically via frontend), options that can be configured by environment variables, and options that are only configurable via Symfony config files. -## User changeable +## User configruation The following things can be changed for every user and a user can change it for himself (if he has the correct permission for it). Configuration is either possible via the user's own settings page (where you can also change the password) or via @@ -24,15 +24,34 @@ the user admin page: * **Preferred currency**: One of the defined currencies, in which all prices should be shown, if possible. Prices with other currencies will be converted to the price selected here +## System configuration (via web interface) + +Many common configuration options can be changed via the web interface. You can find the settings page in the sidebar under +"System" -> "Settings". You need to have the "Change system settings" permission to access this page. + +If a setting is greyed out and cannot be changed, it means that this setting is currently overwritten by an environment +variable. You can either change the environment variable to change the setting, or you can migrate the setting to the +database, so that it can be changed via the web interface. To do this, you can use the `php bin/console settings:migrate-env-to-settings` command +and remove the environment variable afterward. + ## Environment variables (.env.local) The following configuration options can only be changed by the server administrator, by either changing the server variables, changing the `.env.local` file or setting env for your docker container. Here are just the most important options listed, see `.env` file for the full list of possible env variables. +Environment variables allow to overwrite settings in the web interface. This is useful, if you want to enforce certain +settings to be unchangable by users, or if you want to configure settings in a central place in a deployed environment. +On the settings page, you can hover over a setting to see, which environment variable can be used to overwrite it, it +is shown as tooltip. API keys or similar sensitve data which is overwritten by env variables, are redacted on the web +interface, so that even administrators cannot see them (only the last 2 characters and the length). + +For technical and security reasons some settings can only be configured via environment variables and not via the web +interface. These settings are marked with "(env only)" in the description below. + ### General options -* `DATABASE_URL`: Configures the database which Part-DB uses: +* `DATABASE_URL` (env only): 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 @@ -42,10 +61,10 @@ options listed, see `.env` file for the full list of possible env variables. 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 +* `DATABASE_MYSQL_USE_SSL_CA` (env only): If this value is set to `1` or `true` and a MySQL connection is used, then the connection is encrypted by SSL/TLS and the server certificate is verified against the system CA certificates or the CA certificate bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept all certificates. -* `DATABASE_EMULATE_NATURAL_SORT` (default 0): If set to 1, Part-DB will emulate natural sorting, even if the database +* `DATABASE_EMULATE_NATURAL_SORT` (default 0) (env only): If set to 1, Part-DB will emulate natural sorting, even if the database does not support it natively. However this is much slower than the native sorting, and contain bugs or quirks, so use it only, if you have to. * `DEFAULT_LANG`: The default language to use server-wide (when no language is explicitly specified by a user or via @@ -74,7 +93,7 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept to specify the size in kilobytes, megabytes or gigabytes. By default `100M` (100 megabytes). Please note that this is only the limit of Part-DB. You still need to configure the php.ini `upload_max_filesize` and `post_max_size` to allow bigger files to be uploaded. -* `DEFAULT_URI`: The default URI base to use for the Part-DB, when no URL can be determined from the browser request. +* `DEFAULT_URI` (env only): The default URI base to use for the Part-DB, when no URL can be determined from the browser request. This should be the primary URL/Domain, which is used to access Part-DB. This value is used to create correct links in emails and other places, where the URL is needed. It is also used, when SAML is enabled.s If you are using a reverse proxy, you should set this to the URL of the reverse proxy (e.g. `https://part-db.example.com`). **This value must end @@ -91,12 +110,14 @@ 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, +* `APP_SECRET` (env only): This variable is a configuration parameter used for various security-related purposes, particularly for securing and protecting various aspects of your application. It's a secret key that is used for cryptographic operations and security measures (session management, CSRF protection, etc..). Therefore this value should be handled as confidential data and not shared publicly. +* `SHOW_PART_IMAGE_OVERLAY`: Set to 0 to disable the part image overlay, which appears if you hover over an image in the + part image gallery -### E-Mail settings +### E-Mail settings (all env only) * `MAILER_DSN`: You can configure the mail provider which should be used for email delivery ( see https://symfony.com/doc/current/components/mailer.html for full documentation). If you just want to use an SMTP @@ -115,7 +136,7 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept * `TABLE_PARTS_DEFAULT_COLUMNS`: The columns in parts tables, which are visible by default (when loading table for first time). Also specify the default order of the columns. This is a comma separated list of column names. Available columns - are: `name`, `id`, `ipn`, `description`, `category`, `footprint`, `manufacturer`, `storage_location`, `amount`, `minamount`, `partUnit`, `addedDate`, `lastModified`, `needs_review`, `favorite`, `manufacturing_status`, `manufacturer_product_number`, `mass`, `tags`, `attachments`, `edit`. + are: `name`, `id`, `ipn`, `description`, `category`, `footprint`, `manufacturer`, `storage_location`, `amount`, `minamount`, `partUnit`, `partCustomState`, `addedDate`, `lastModified`, `needs_review`, `favorite`, `manufacturing_status`, `manufacturer_product_number`, `mass`, `tags`, `attachments`, `edit`. ### History/Eventlog-related settings @@ -136,7 +157,7 @@ The following options are used to configure, which (and how much) data is writte If you want to use want to revert changes or view older revisions of entities, then `HISTORY_SAVE_CHANGED_FIELDS`, `HISTORY_SAVE_CHANGED_DATA` and `HISTORY_SAVE_REMOVED_DATA` all have to be true. -### Error pages settings +### Error pages settings (all env only) * `ERROR_PAGE_ADMIN_EMAIL`: You can set an email address here, which is shown on the error page, who should be contacted about the issue (e.g. an IT support email of your company) @@ -151,7 +172,7 @@ then `HISTORY_SAVE_CHANGED_FIELDS`, `HISTORY_SAVE_CHANGED_DATA` and `HISTORY_SAV All parts in the selected category and all subcategories are shown in KiCad. Set this to a higher value, if you want to show more categories in KiCad. When you set this value to -1, all parts are shown inside a single category in KiCad. -### SAML SSO settings +### SAML SSO settings (all env only) The following settings can be used to enable and configure Single-Sign on via SAML. This allows users to log in to Part-DB without entering a username and password, but instead they are redirected to a SAML Identity Provider (IdP) and @@ -199,26 +220,26 @@ See the [information providers]({% link usage/information_provider_system.md %}) ### Other / less-used options -* `TRUSTED_PROXIES`: Set the IP addresses (or IP blocks) of trusted reverse proxies here. This is needed to get correct +* `TRUSTED_PROXIES` (env only): Set the IP addresses (or IP blocks) of trusted reverse proxies here. This is needed to get correct IP information (see [here](https://symfony.com/doc/current/deployment/proxies.html) for more info). -* `TRUSTED_HOSTS`: To prevent `HTTP Host header attacks` you can set a regex containing all host names via which Part-DB +* `TRUSTED_HOSTS` (env only): To prevent `HTTP Host header attacks` you can set a regex containing all host names via which Part-DB should be accessible. If accessed via the wrong hostname, an error will be shown. -* `DEMO_MODE`: Set Part-DB into demo mode, which forbids users to change their passwords and settings. Used for the demo +* `DEMO_MODE` (env only): Set Part-DB into demo mode, which forbids users to change their passwords and settings. Used for the demo instance. This should not be needed for normal installations. -* `NO_URL_REWRITE_AVAILABLE` (allowed values `true` or `false`): Set this value to true, if your webserver does not +* `NO_URL_REWRITE_AVAILABLE` (allowed values `true` or `false`) (env only): Set this value to true, if your webserver does not support rewrite. In this case, all URL paths will contain index.php/, which is needed then. Normally this setting does not need to be changed. -* `REDIRECT_TO_HTTPS`: If this is set to true, all requests to http will be redirected to https. This is useful if your +* `REDIRECT_TO_HTTPS` (env only): If this is set to true, all requests to http will be redirected to https. This is useful if your web server does not already do this (like the one used in the demo instance). If your web server already redirects to https, you don't need to set this. Ensure that Part-DB is accessible via HTTPS before you enable this setting. * `FIXER_API_KEY`: If you want to automatically retrieve exchange rates for base currencies other than euros, you have to configure an exchange rate provider API. [Fixer.io](https://fixer.io/) is preconfigured, and you just have to register there and set the retrieved API key in this environment variable. -* `APP_ENV`: This value should always be set to `prod` in normal use. Set it to `dev` to enable debug/development +* `APP_ENV` (env only): This value should always be set to `prod` in normal use. Set it to `dev` to enable debug/development mode. (**You should not do this on a publicly accessible server, as it will leak sensitive information!**) * `BANNER`: You can configure the text that should be shown as the banner on the homepage. Useful especially for docker containers. In all other applications you can just change the `config/banner.md` file. -* `DISABLE_YEAR2038_BUG_CHECK`: If set to `1`, the year 2038 bug check is disabled on 32-bit systems, and dates after +* `DISABLE_YEAR2038_BUG_CHECK` (env only): If set to `1`, the year 2038 bug check is disabled on 32-bit systems, and dates after 2038 are no longer forbidden. However this will lead to 500 error messages when rendering dates after 2038 as all current 32-bit PHP versions can not format these dates correctly. This setting is for the case that future PHP versions will handle this correctly on 32-bit systems. 64-bit systems are not affected by this bug, and the check is always disabled. @@ -226,7 +247,7 @@ handle this correctly on 32-bit systems. 64-bit systems are not affected by this ## Banner To change the banner you can find on the homepage, you can either set the `BANNER` environment variable to the text you -want to show, or you can edit the `config/banner.md` file. The banner is written in markdown, so you can use all +want to show, or change it in the system settings webinterface. The banner is written in markdown, so you can use all markdown (and even some subset of HTML) syntax to format the text. ## parameters.yaml @@ -241,8 +262,6 @@ command `bin/console cache:clear`. The following options are available: -* `partdb.global_theme`: The default theme to use, when no user specific theme is set. Should be one of the themes from - the `partdb.available_themes` config option. * `partdb.locale_menu`: The codes of the languages, which should be shown in the language chooser menu (the one with the user icon in the navbar). The first language in the list will be the default language. * `partdb.gdpr_compliance`: When set to true (default value), IP addresses which are saved in the database will be diff --git a/docs/installation/choosing_database.md b/docs/installation/choosing_database.md index 69dc810e1..cd9657d4f 100644 --- a/docs/installation/choosing_database.md +++ b/docs/installation/choosing_database.md @@ -150,9 +150,9 @@ In the `serverVersion` parameter you can specify the version of the PostgreSQL s The `charset` parameter specify the character set of the database. It should be set to `utf8` to ensure that all characters are stored correctly. -If you want to use a unix socket for the connection instead of a TCP connnection, you can specify the socket path in the `unix_socket` parameter. +If you want to use a unix socket for the connection instead of a TCP connnection, you can specify the socket path in the `host` parameter. ```shell -DATABASE_URL="postgresql://db_user:db_password@localhost/db_name?serverVersion=12.19&charset=utf8&unix_socket=/var/run/postgresql/.s.PGSQL.5432" +DATABASE_URL="postgresql://db_user@localhost/db_name?serverVersion=16.6&charset=utf8&host=/var/run/postgresql" ``` diff --git a/docs/installation/installation_docker.md b/docs/installation/installation_docker.md index 5d357ae53..232633abb 100644 --- a/docs/installation/installation_docker.md +++ b/docs/installation/installation_docker.md @@ -47,6 +47,12 @@ services: - DATABASE_URL=sqlite:///%kernel.project_dir%/var/db/app.db # In docker env logs will be redirected to stderr - APP_ENV=docker + + # Uncomment this, if you want to use the automatic database migration feature. With this you have you do not have to + # run the doctrine:migrations:migrate commands on installation or upgrade. A database backup is written to the uploads/ + # folder (under .automigration-backup), so you can restore it, if the migration fails. + # This feature is currently experimental, so use it at your own risk! + # - DB_AUTOMIGRATE=true # You can configure Part-DB using environment variables # Below you can find the most essential ones predefined @@ -130,28 +136,18 @@ services: # In docker env logs will be redirected to stderr - APP_ENV=docker - # You can configure Part-DB using environment variables - # Below you can find the most essential ones predefined + # Uncomment this, if you want to use the automatic database migration feature. With this you have you do not have to + # run the doctrine:migrations:migrate commands on installation or upgrade. A database backup is written to the uploads/ + # folder (under .automigration-backup), so you can restore it, if the migration fails. + # This feature is currently experimental, so use it at your own risk! + # - DB_AUTOMIGRATE=true + + # You can configure Part-DB using the webUI or environment variables # However you can add add any other environment configuration you want here # See .env file for all available options or https://docs.part-db.de/configuration.html - # The language to use serverwide as default (en, de, ru, etc.) - - DEFAULT_LANG=en - # The default timezone to use serverwide (e.g. Europe/Berlin) - - DEFAULT_TIMEZONE=Europe/Berlin - # 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 - - BASE_CURRENCY=EUR - # The name of this installation. This will be shown as title in the browser and in the header of the website - - INSTANCE_NAME=Part-DB - - # Allow users to download attachments to the server by providing an URL - # This could be a potential security issue, as the user can retrieve any file the server has access to (via internet) - - ALLOW_ATTACHMENT_DOWNLOADS=0 - # Use gravatars for user avatars, when user has no own avatar defined - - USE_GRAVATAR=0 - # Override value if you want to show to show a given text on homepage. - # When this is empty the content of config/banner.md is used as banner + # When this is outcommented the webUI can be used to configure the banner #- BANNER=This is a test banner
with a line break database: @@ -201,6 +197,10 @@ You also have to create the database as described above in step 4. You can run the console commands described in README by executing `docker exec --user=www-data -it partdb bin/console [command]` +{: .warning } +> If you run a root console inside the container, and wanna execute commands on the webserver behalf, be sure to use `sudo -E` command (with the `-E` flag) to preserve env variables from the current shell. +> Otherwise Part-DB console might use the wrong configuration to execute commands. + ## Troubleshooting *Login is not possible. Login page is just reloading and no error message is shown or something like "CSFR token invalid"*: diff --git a/docs/installation/installation_guide-debian.md b/docs/installation/installation_guide-debian.md index 885eea901..b3c61126d 100644 --- a/docs/installation/installation_guide-debian.md +++ b/docs/installation/installation_guide-debian.md @@ -1,13 +1,13 @@ --- -title: Direct Installation on Debian 11 +title: Direct Installation on Debian 12 layout: default parent: Installation nav_order: 4 --- -# Part-DB installation guide for Debian 11 (Bullseye) +# Part-DB installation guide for Debian 12 (Bookworm) -This guide shows you how to install Part-DB directly on Debian 11 using apache2 and SQLite. This guide should work with +This guide shows you how to install Part-DB directly on Debian 12 using apache2 and SQLite. This guide should work with recent Ubuntu and other Debian-based distributions with little to no changes. Depending on what you want to do, using the prebuilt docker images may be a better choice, as you don't need to install this many dependencies. See [here]({% link installation/installation_docker.md %}) for more information on the docker @@ -28,40 +28,32 @@ It is recommended to install Part-DB on a 64-bit system, as the 32-bit version o For the installation of Part-DB, we need some prerequisites. They can be installed by running the following command: ```bash -sudo apt install git curl zip ca-certificates software-properties-common apt-transport-https lsb-release nano wget +sudo apt update && apt upgrade +sudo apt install git curl zip ca-certificates software-properties-common \ + apt-transport-https lsb-release nano wget sqlite3 ``` +Please run `sqlite3 --version` to assert that the SQLite version is 3.35 or higher. +Otherwise some database migrations will not succeed. + ### Install PHP and apache2 -Part-DB is written in [PHP](https://php.net) and therefore needs a PHP interpreter to run. Part-DB needs PHP 8.1 or +Part-DB is written in [PHP](https://php.net) and therefore needs a PHP interpreter to run. Part-DB needs PHP 8.2 or higher. However, it is recommended to use the most recent version of PHP for performance reasons and future compatibility. -As Debian 11 does not ship PHP 8.1 in its default repositories, we have to add a repository for it. You can skip this -step if your distribution is shipping a recent version of PHP or you want to use the built-in PHP version. If you are -using Debian 12, you can skip this step, as PHP 8.1 is already included in the default repositories. - -```bash -# Add sury repository for PHP 8.1 -sudo curl -sSL https://packages.sury.org/php/README.txt | sudo bash -x - -# Update package list -sudo apt update && sudo apt upgrade -``` - -Now you can install PHP 8.1 and the required packages (change the 8.1 in the package version according to the version you -want to use): +Install PHP with required extensions and apache2: ```bash -sudo apt install php8.1 libapache2-mod-php8.1 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 +sudo apt install apache2 php8.2 libapache2-mod-php8.2 \ + php8.2-opcache php8.2-curl php8.2-gd php8.2-mbstring \ + php8.2-xml php8.2-bcmath php8.2-intl php8.2-zip php8.2-xsl \ + php8.2-sqlite3 php8.2-mysql ``` -The apache2 webserver should be already installed with this command and configured basically. - ### Install composer -Part-DB uses [composer](https://getcomposer.org/) to install required PHP libraries. As the version shipped in the -repositories is pretty old, we will install it manually: +Part-DB uses [composer](https://getcomposer.org/) to install required PHP libraries. Install the latest version manually: ```bash # Download composer installer script @@ -78,10 +70,9 @@ To build the front end (the user interface) Part-DB uses [yarn](https://yarnpkg. shipped versions are pretty old, we install new versions from the official Node.js repository: ```bash -# Add recent node repository (nodejs 18 is supported until 2025) -curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash - -# Install nodejs -sudo apt install nodejs +curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - +sudo apt install -y nodejs + ``` We can install yarn with the following commands: @@ -117,8 +108,8 @@ Alternatively, you can check out a specific version by running ( see [GitHub Releases page](https://github.com/Part-DB/Part-DB-server/releases) for a list of available versions): ```bash -# This checks out the version 1.5.2 -git checkout v1.5.2 +# This checks out the version 2.0.0 +git checkout v2.0.0 ``` Change ownership of the files to the apache user: @@ -142,11 +133,10 @@ configuration: cp .env .env.local ``` -In your `.env.local` you can configure Part-DB according to your wishes. A full list of configuration options can be -found [here](../configuration.md). -Other configuration options like the default language or default currency can be found in `config/parameters.yaml`. +In your `.env.local` you can configure Part-DB according to your wishes and overwrite web interface settings. +A full list of configuration options can be found [here](../configuration.md). -Please check that the `partdb.default_currency` value in `config/parameters.yaml` matches your mainly used currency, as +Please check that the configured base currency matches your mainly used currency, as this can not be changed after creating price information. ### Install dependencies for Part-DB and build frontend @@ -256,6 +246,7 @@ network to point to the server). Navigate to the Part-DB web interface and login via the user icon in the top right corner. You can log in using the username `admin` and the password you have written down earlier. +As first steps, you should check out the system settings and check if everything is correct. ## Update Part-DB @@ -291,7 +282,7 @@ sudo -u www-data php bin/console cache:clear ## MySQL/MariaDB database To use a MySQL database, follow the steps from above (except the creation of the database, we will do this later). -Debian 11 does not ship MySQL in its repositories anymore, so we use the compatible MariaDB instead: +Debian 12 does not ship MySQL in its repositories anymore, so we use the compatible MariaDB instead: 1. Install maria-db with: diff --git a/docs/installation/nginx.md b/docs/installation/nginx.md index 82a2e4cf7..84305975f 100644 --- a/docs/installation/nginx.md +++ b/docs/installation/nginx.md @@ -52,6 +52,11 @@ server { location ~ \.php$ { return 404; } + + # Set Content-Security-Policy for svg files, to block embedded javascript in there + location ~* \.svg$ { + add_header Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none';"; + } error_log /var/log/nginx/parts.error.log; access_log /var/log/nginx/parts.access.log; diff --git a/docs/upgrade/1_to_2.md b/docs/upgrade/1_to_2.md new file mode 100644 index 000000000..c333136ad --- /dev/null +++ b/docs/upgrade/1_to_2.md @@ -0,0 +1,102 @@ +--- +layout: default +title: Upgrade from Part-DB 1.x to 2.x +nav_order: 1 +has_children: false +parent: Upgrade +--- + +# Upgrade from Part-DB 1.x to 2.x + +Part-DB 2.0 is a major release that changes a lot of things internally, but it is still compatible with Part-DB 1.x. +Depending on your preferences, you will have to do some changes to your Part-DB installation, this document will guide +you through the upgrade process. + +## New requirements +*If you are running Part-DB inside a docker container, you can skip this section, as the new requirements are already +fulfilled by the official Part-DB docker image.* + +Part-DB 2.0 requires at least PHP 8.2 (newer versions are recommended). So if your existing Part-DB installation is still +running PHP 8.1, you will have to upgrade your PHP version first. +The minimum required version of node.js is now 20.0 or newer, so if you are using 18.0, you will have to upgrade it too. + +Most distributions should have the possibility to get backports for PHP 8.4 and modern nodejs, so you should be able to +easily upgrade your system to the new requirements. Otherwise, you can use the official Part-DB docker image, which +ships all required dependencies and is always up to date with the latest requirements, so that you do not have to worry +about the requirements at all. + +## Changes +* Configuration is now preferably done via a web settings interface. You can still use environment variables, these overwrite +the settings in the web interface. Existing configuration will still work, but you should consider migriting them to the +web interface as described below. +* The `config/banner.md` file that could been used to customize the banner text, was removed. You can now set the banner text + directly in the admin interface, or by setting the `BANNER` environment variable. If you want to keep your existing + banner text, you will have to copy it from the `config/banner.md` file to the admin interface or set the `BANNER` + environment variable. +* The parameters `partdb.sidebar.items`, `partdb.sidebar.root_node_enable` and `partdb.sidebar.root_expanded` in `config/parameters.yaml`, +were removed. You can configure them now directly in the admin interface. +* Updated icon set. As fontawesome 7 is now used, some icons have changed slightly. + +## Upgrade installation + +The upgrade process works very similar to a normal (minor release) upgrade. + +### Direct installation + +**Be sure to execute the following steps as the user that owns the Part-DB files (e.g. `www-data`, or your webserver user). So prepend a `sudo -u wwww-data` where necessary.** + +1. Make a backup of your existing Part-DB installation, including the database, data directories and the configuration files and `.env.local` file. +The `php bin/console partdb:backup` command can help you with this. +2. Pull the v2 version. For git installation you can do this with `git checkout v2.0.0` (or newer version) +3. Remove the `var/cache/` directory inside the Part-DB installation to ensure that no old cache files remain. +4. Run `composer install --no-dev -o` to update the dependencies. +5. Run `yarn install` and `yarn build` to update the frontend assets. +6. Rund `php bin/console doctrine:migrations:migrate` to update the database schema. +7. Clear the cache with `php bin/console cache:clear`. +8. Open your Part-DB instance in the browser and log in as an admin user. +9. Go to the user or group permissions page, and give yourself (and other administrators) the right to change system settings (under "System" and "Configuration"). +10. You can now go to the settings page (under "System" and "Settings") and check if all settings are correct. +11. Parameters which were previously set via environment variables are greyed out and cannot be changed in the web interface. +If you want to change them, you must migrate them to the settings interface as described below. + +### Docker installation +1. Make a backup of your existing Part-DB installation, including the database, data directories and the configuration files and the file where you configure the docker environment variables. +2. Stop the existing Part-DB container with `docker compose down` +3. Ensure that your docker compose file uses the new latest images (either `latest` or `2` tag). +4. Pull the new images with `docker compose pull` and start the container with `docker compose up -d` +5. If you have database automigration disabled, run `docker exec --user=www-data partdb php bin/console doctrine:migrations:migrate` to update the database schema. +6. Open your Part-DB instance in the browser and log in as an admin user. +7. Go to the user or group permissions page, and give yourself (and other administrators) +the right to change system settings (under "System" and "Configuration"). +8. You can now go to the settings page (under "System" and "Settings") +9. Parameters which were previously set via environment variables are greyed out and cannot be changed in the web interface. +If you want to change them, you must migrate them to the settings interface as described below. + +## Migrate environment variable configuration to settings interface +As described above, configuration can now be done via the web interface, and can be overwritten by environment variables, so +that existing configuration should still work. However, if a parameter is set via an environment variable, it cannot be changed in the web interface. +To change it, you must migrate your environment variable configuration to the new system. + +For this there is the new console command `settings:migrate-env-to-settings`, which reads in all environment variables used to overwrite +settings and write them to the database, so that you can safely delete them from your environment variable configuration afterwards, without +loosing your configuration. + +To run the command, execute `php bin/console settings:migrate-env-to-settings --all` as webserver user (or run `docker exec --user=www-data -it partdb php bin/console settings:migrate-env-to-settings --all` for docker containers). +It will list you all environment variables, it found and ask you for confirmation to migrate them. Answer with `yes` to migrate them and hit enter. + +After the migration run successfully, the contents of your environment variables are now stored in the database and you can safely remove them from your environment variable configuration. +Go through the environment variables listed by the command and remove them from your environment variable configuration (e.g. `.env.local` file or docker compose file), or just comment them out for now. + +If you want to keep some environment variables, just leave them as they are, they will still work as before, the migration command only affects the settings stored in the database. + + +## Troubleshooting + +### cache:clear fails: You have requested a non-existent parameter "jbtronics.settings.proxy_dir". +If you receive an error like +``` +In App_KernelProdContainer.php line 2839: +You have requested a non-existent parameter "jbtronics.settings.proxy_dir". +``` +when running `php bin/console cache:clear` or `composer install`. You have to manually delete the `var/cache/` +directory inside your Part-DB installation and try again. diff --git a/docs/upgrade/index.md b/docs/upgrade/index.md new file mode 100644 index 000000000..95a9cc337 --- /dev/null +++ b/docs/upgrade/index.md @@ -0,0 +1,9 @@ +--- +layout: default +title: Upgrade +nav_order: 7 +has_children: true +--- + +This section provides information on how to upgrade Part-DB to the latest version. +This is intended for major release upgrades, where requirements or things changes significantly. diff --git a/docs/upgrade_legacy.md b/docs/upgrade/upgrade_legacy.md similarity index 97% rename from docs/upgrade_legacy.md rename to docs/upgrade/upgrade_legacy.md index e1e43831a..4dd29e4d7 100644 --- a/docs/upgrade_legacy.md +++ b/docs/upgrade/upgrade_legacy.md @@ -2,6 +2,8 @@ layout: default title: Upgrade from legacy Part-DB version (<1.0) nav_order: 100 +redirect_from: /upgrade_legacy +parent: Upgrade --- # Upgrade from legacy Part-DB version @@ -16,8 +18,8 @@ sections carefully before proceeding to upgrade. ## Changes -* PHP 8.1 or higher is required now (Part-DB 0.5 required PHP 5.4+, Part-DB 0.6 PHP 7.0). - Releases are available for Windows too, so almost everybody should be able to use PHP 8.1 +* PHP 8.2 or higher is required now (Part-DB 0.5 required PHP 5.4+, Part-DB 0.6 PHP 7.0). + Releases are available for Windows too, so almost everybody should be able to use PHP 8.2 * **Console access is highly recommended.** The installation of composer and frontend dependencies require console access, also more sensitive stuff like database migration works via CLI now, so you should have console access on your server. * Markdown/HTML is now used instead of BBCode for rich text in description and command fields. diff --git a/docs/usage/bom_import.md b/docs/usage/bom_import.md index 94a06d550..b4bcb2beb 100644 --- a/docs/usage/bom_import.md +++ b/docs/usage/bom_import.md @@ -34,3 +34,12 @@ select the BOM file you want to import and some options for the import process: has a different format and does not work with this type. You can generate this BOM file by going to "File" -> "Fabrication Outputs" -> "Bill of Materials" in Pcbnew and save the file to your desired location. +* **KiCAD Schematic BOM (CSV file)**: A CSV file of the Bill of Material (BOM) generated + by [KiCAD Eeschema](https://www.kicad.org/). + You can generate this BOM file by going to "Tools" -> "Generate Bill of Materials" in Eeschema and save the file to your + desired location. In the next step you can customize the mapping of the fields in Part-DB, if you have any special fields + in your BOM to locate your fields correctly. +* **Generic CSV file**: A generic CSV file. You can use this option if you use some different ECAD software or wanna create + your own CSV file. You will need to specify at least the designators, quantity and value fields in the CSV. In the next + step you can customize the mapping of the fields in Part-DB, if you have any special fields in your BOM to locate your + parts correctly. diff --git a/docs/usage/console_commands.md b/docs/usage/console_commands.md index 00431a34c..173f7b78f 100644 --- a/docs/usage/console_commands.md +++ b/docs/usage/console_commands.md @@ -25,6 +25,12 @@ is named `partdb`, you can execute the command `php bin/console cache:clear` wit docker exec --user=www-data partdb php bin/console cache:clear ``` +{: .warning } +> If you run a root console inside the docker container, and wanna execute commands on the webserver behalf, be sure to use `sudo -E` command (with the `-E` flag) to preserve env variables from the current shell. +> Otherwise Part-DB console might use the wrong configuration to execute commands. + +## Troubleshooting + ## User management commands * `php bin/console partdb:users:list`: List all users of this Part-DB instance @@ -60,8 +66,16 @@ docker exec --user=www-data partdb php bin/console cache:clear * `partdb:migrations:import-partkeepr`: Imports a mysqldump XML dump of a PartKeepr database into Part-DB. This is only needed for users, which want to migrate from PartKeepr to Part-DB. *All existing data in the Part-DB database is deleted!* +* `settings:migrate-env-to-settings`: Migrate configuration from environment variables to the settings interface. +The value of the environment variable is copied to the settings database, so the environment variable can be removed afterwards without losing the configuration. ## Database commands * `php bin/console doctrine:migrations:migrate`: Migrate the database to the latest version -* `php bin/console doctrine:migrations:up-to-date`: Check if the database is up-to-date \ No newline at end of file +* `php bin/console doctrine:migrations:up-to-date`: Check if the database is up-to-date + +## Attachment commands + +* `php bin/console partdb:attachments:download`: Download all attachments, which are not already downloaded, to the + local filesystem. This is useful to create local backups of the attachments, no matter what happens on the remote and + also makes pictures thumbnails available for the frontend for them diff --git a/docs/usage/getting_started.md b/docs/usage/getting_started.md index 4bb8afb91..4b9a809a3 100644 --- a/docs/usage/getting_started.md +++ b/docs/usage/getting_started.md @@ -12,11 +12,19 @@ Before starting, it's useful to read a bit about the [concepts of Part-DB]({% li 1. TOC {:toc} -## Customize config files +## Customize system settings -Before you start creating data structures, you should configure Part-DB to your needs by changing possible configuration -options. -This is done either via changing the `.env.local` file in a direct installation or by changing the env variables in +Before starting creating datastructures, you should check the system settings to ensure that they fit your needs. +After login as an administrator, you can find the settings in the sidebar under `Tools -> System -> Settings`. +![image]({% link assets/getting_started/system_settings.png %}) + +Here you can change various settings, like the name of your Part-DB instance (which is shown in the title bar of the +browser), the default language (which is used if no user preference is set), the default timezone (which is used to +display times correctly), the default currency (which is used to display prices correctly), and many more. + +Some more fundamental settings like database connection, mail server settings, SSO, etc. are configured via environment variables. +Environment variables also allow to overwrite various settings from the web interface. +Environment variables can be changed by editing the `.env.local` file in a direct installation or by changing the env variables in your `docker-compose.yaml` file. A list of possible configuration options can be found [here]({% link configuration.md %}). @@ -44,8 +52,8 @@ used. ## (Optional) Customize homepage banner -The banner which is shown on the homepage, can be customized/changed by changing the `config/banner.md` file with a text -editor. You can use markdown and (safe) HTML here, to style and customize the banner. +The banner which is shown on the homepage, can be customized/changed via the homepage banner setting in system settings. +You can use markdown and (safe) HTML here, to style and customize the banner. You can even use LaTeX-style equations by wrapping the expressions into `$` (like `$E=mc^2$`, which is rendered inline: $E=mc^2$) or `$$` (like `$$E=mc^2$$`) which will be rendered as a block, like so: $$E=mc^2$$ @@ -202,4 +210,4 @@ later. You can choose from your created datastructures to add manufacturer information, supplier information, etc. to the part. You can also create new datastructures on the fly, if you want to add additional information to the part, by typing the name of the new datastructure in the field and select the "New ..." option in the dropdown menu. See [tips]({% link -usage/tips_tricks.md %}) for more information. \ No newline at end of file +usage/tips_tricks.md %}) for more information. diff --git a/docs/usage/import_export.md b/docs/usage/import_export.md index e43936cc8..136624e29 100644 --- a/docs/usage/import_export.md +++ b/docs/usage/import_export.md @@ -20,7 +20,7 @@ Part-DB. Data can also be exported from Part-DB into various formats. > individually in the permissions settings. If you want to import data from PartKeepr you might want to look into the [PartKeepr migration guide]({% link -upgrade_legacy.md %}). +upgrade/upgrade_legacy.md %}). ### Import parts @@ -142,6 +142,9 @@ You can select between the following export formats: efficiently. * **YAML** (Yet Another Markup Language): Very similar to JSON * **XML** (Extensible Markup Language): Good support with nested data structures. Similar use cases as JSON and YAML. +* **Excel**: Similar to CSV, but in a native Excel format. Can be opened in Excel and LibreOffice Calc. Does not support nested + data structures or sub-data (like parameters, attachments, etc.), very well (many columns are generated, as every + possible sub-data is exported as a separate column). Also, you can select between the following export levels: @@ -158,4 +161,4 @@ information, this can lead to very large export files. You can export parts in all part tables. Select the parts you want via the checkbox in the table line and select the export format and level in the appearing menu. -See the section about exporting data structures for more information about the export formats and levels. \ No newline at end of file +See the section about exporting data structures for more information about the export formats and levels. diff --git a/docs/usage/information_provider_system.md b/docs/usage/information_provider_system.md index a6102d6cb..bc6fe76e2 100644 --- a/docs/usage/information_provider_system.md +++ b/docs/usage/information_provider_system.md @@ -68,6 +68,13 @@ If you already have attachment types for images and datasheets and want the info can add the alternative names "Datasheet" and "Image" to the alternative names field of the attachment types. +## Bulk import + +If you want to update the information of multiple parts, you can use the bulk import system: Go to a part table and select +the parts you want to update. In the bulk actions dropdown select "Bulk info provider import" and click "Apply". +You will be redirected to a page, where you can select how part fields should be mapped to info provider fields, and the +results will be shown. + ## Data providers The system tries to be as flexible as possible, so many different information sources can be used. @@ -80,6 +87,11 @@ Normally the providers utilize an API of a service, and you need to create an ac Also, there are limits on how many requests you can do per day or month, depending on the provider and your contract with them. +Data providers can be either configured in the system settings (in the info provider tab) or on the settings page which is +reachable via the cogwheel symbol next to the provider in the provider list. It is also possible to configure them via +environment variables. See below for the available configuration options. API keys configured via environment variables +are redacted in the settings interface. + The following providers are currently available and shipped with Part-DB: (All trademarks are property of their respective owners. Part-DB is not affiliated with any of the companies.) @@ -232,6 +244,26 @@ The following env configuration options are available: 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. +### Reichelt + +The reichelt provider uses webscraping from [reichelt.com](https://reichelt.com/) to get part information. +This is not an official API and could break at any time. So use it at your own risk. + +The following env configuration options are available: +* `PROVIDER_REICHELT_ENABLED`: Set this to `1` to enable the Reichelt provider +* `PROVIDER_REICHELT_CURRENCY`: The currency you want to get prices in. Only possible for countries which use Non-EUR (optional, default: `EUR`) +* `PROVIDER_REICHELT_COUNTRY`: The country you want to get the prices for (optional, default: `DE`) +* `PROVIDER_REICHELT_LANGUAGE`: The language you want to get the descriptions in (optional, default: `en`) +* `PROVIDER_REICHELT_INCLUDE_VAT`: If set to `1`, the prices will be gross prices (including tax), otherwise net prices (optional, default: `1`) + +### Pollin + +The pollin provider uses webscraping from [pollin.de](https://www.pollin.de/) to get part information. +This is not an official API and could break at any time. So use it at your own risk. + +The following env configuration options are available: +* `PROVIDER_POLLIN_ENABLED`: Set this to `1` to enable the Pollin provider + ### Custom provider To create a custom provider, you have to create a new class implementing the `InfoProviderInterface` interface. As long diff --git a/docs/usage/keybindings.md b/docs/usage/keybindings.md index 698524c5a..771d7684e 100644 --- a/docs/usage/keybindings.md +++ b/docs/usage/keybindings.md @@ -117,6 +117,6 @@ For a German keyboard layout, replace `[` with `0`, and `]` with `ยด`. | Key | Character | |--------------------------------|--------------------| | **Alt + [** (code 219) | ยฉ (Copyright char) | -| **Alt + Shift + [** (code 219) | (Registered char) | +| **Alt + Shift + [** (code 219) | ยฎ (Registered char) | | **Alt + ]** (code 221) | โ„ข (Trademark char) | -| **Alt + Shift + ]** (code 221) | (Degree char) | +| **Alt + Shift + ]** (code 221) | ยฐ (Degree char) | diff --git a/docs/usage/tips_tricks.md b/docs/usage/tips_tricks.md index d033cbe87..6eda718d2 100644 --- a/docs/usage/tips_tricks.md +++ b/docs/usage/tips_tricks.md @@ -95,4 +95,9 @@ It is only be shown to users which has the `Show available Part-DB updates` perm For the notification to work, Part-DB queries the GitHub API every 2 days to check for new releases. No data is sent to GitHub besides the metadata required for the connection (so the public IP address of your computer running Part-DB). If you don't want Part-DB to query the GitHub API, or if your server can not reach the internet, you can disable the -update notifications by setting the `CHECK_FOR_UPDATES` option to `false`. \ No newline at end of file +update notifications by setting the `CHECK_FOR_UPDATES` option to `false`. + +## Internet access via proxy +If you server running Part-DB does not have direct access to the internet, but has to use a proxy server, you can configure +the proxy settings in the `.env.local` file (or docker env config). You can set the `HTTP_PROXY` and `HTTPS_PROXY` environment +variables to the URL of your proxy server. If your proxy server requires authentication, you can include the username and password in the URL. diff --git a/makefile b/makefile new file mode 100644 index 000000000..bc4d0bf3e --- /dev/null +++ b/makefile @@ -0,0 +1,91 @@ +# PartDB Makefile for Test Environment Management + +.PHONY: help deps-install lint format format-check test coverage pre-commit all test-typecheck \ +test-setup test-clean test-db-create test-db-migrate test-cache-clear test-fixtures test-run test-reset \ +section-dev dev-setup dev-clean dev-db-create dev-db-migrate dev-cache-clear dev-warmup dev-reset + +# Default target +help: ## Show this help + @awk 'BEGIN {FS = ":.*##"}; /^[a-zA-Z0-9][a-zA-Z0-9_-]+:.*##/ {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) + +# Dependencies +deps-install: ## Install PHP dependencies with unlimited memory + @echo "๐Ÿ“ฆ Installing PHP dependencies..." + COMPOSER_MEMORY_LIMIT=-1 composer install + yarn install + @echo "โœ… Dependencies installed" + +# Complete test environment setup +test-setup: test-clean test-db-create test-db-migrate test-fixtures ## Complete test setup (clean, create DB, migrate, fixtures) + @echo "โœ… Test environment setup complete!" + +# Clean test environment +test-clean: ## Clean test cache and database files + @echo "๐Ÿงน Cleaning test environment..." + rm -rf var/cache/test + rm -f var/app_test.db + @echo "โœ… Test environment cleaned" + +# Create test database +test-db-create: ## Create test database (if not exists) + @echo "๐Ÿ—„๏ธ Creating test database..." + -php bin/console doctrine:database:create --if-not-exists --env test || echo "โš ๏ธ Database creation failed (expected for SQLite) - continuing..." + +# Run database migrations for test environment +test-db-migrate: ## Run database migrations for test environment + @echo "๐Ÿ”„ Running database migrations..." + COMPOSER_MEMORY_LIMIT=-1 php bin/console doctrine:migrations:migrate -n --env test + +# Clear test cache +test-cache-clear: ## Clear test cache + @echo "๐Ÿ—‘๏ธ Clearing test cache..." + rm -rf var/cache/test + @echo "โœ… Test cache cleared" + +# Load test fixtures +test-fixtures: ## Load test fixtures + @echo "๐Ÿ“ฆ Loading test fixtures..." + php bin/console partdb:fixtures:load -n --env test + +# Run PHPUnit tests +test-run: ## Run PHPUnit tests + @echo "๐Ÿงช Running tests..." + php bin/phpunit + +# Quick test reset (clean + migrate + fixtures, skip DB creation) +test-reset: test-cache-clear test-db-migrate test-fixtures + @echo "โœ… Test environment reset complete!" + +test-typecheck: ## Run static analysis (PHPStan) + @echo "๐Ÿงช Running type checks..." + COMPOSER_MEMORY_LIMIT=-1 composer phpstan + +# Development helpers +dev-setup: dev-clean dev-db-create dev-db-migrate dev-warmup ## Complete development setup (clean, create DB, migrate, warmup) + @echo "โœ… Development environment setup complete!" + +dev-clean: ## Clean development cache and database files + @echo "๐Ÿงน Cleaning development environment..." + rm -rf var/cache/dev + rm -f var/app_dev.db + @echo "โœ… Development environment cleaned" + +dev-db-create: ## Create development database (if not exists) + @echo "๐Ÿ—„๏ธ Creating development database..." + -php bin/console doctrine:database:create --if-not-exists --env dev || echo "โš ๏ธ Database creation failed (expected for SQLite) - continuing..." + +dev-db-migrate: ## Run database migrations for development environment + @echo "๐Ÿ”„ Running database migrations..." + COMPOSER_MEMORY_LIMIT=-1 php bin/console doctrine:migrations:migrate -n --env dev + +dev-cache-clear: ## Clear development cache + @echo "๐Ÿ—‘๏ธ Clearing development cache..." + rm -rf var/cache/dev + @echo "โœ… Development cache cleared" + +dev-warmup: ## Warm up development cache + @echo "๐Ÿ”ฅ Warming up development cache..." + COMPOSER_MEMORY_LIMIT=-1 php -d memory_limit=1G bin/console cache:warmup --env dev -n + +dev-reset: dev-cache-clear dev-db-migrate ## Quick development reset (cache clear + migrate) + @echo "โœ… Development environment reset complete!" \ No newline at end of file diff --git a/migrations/Version20221114193325.php b/migrations/Version20221114193325.php index 9766ccf35..bc2a97fa9 100644 --- a/migrations/Version20221114193325.php +++ b/migrations/Version20221114193325.php @@ -4,18 +4,15 @@ namespace DoctrineMigrations; +use App\Doctrine\Migration\ContainerAwareMigrationInterface; use App\Migration\AbstractMultiPlatformMigration; use App\Migration\WithPermPresetsTrait; use App\Services\UserSystem\PermissionPresetsHelper; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Schema\Schema; use Psr\Log\LoggerInterface; -use Symfony\Component\DependencyInjection\ContainerAwareInterface; -/** - * Auto-generated Migration: Please modify to your needs! - */ -final class Version20221114193325 extends AbstractMultiPlatformMigration implements ContainerAwareInterface +final class Version20221114193325 extends AbstractMultiPlatformMigration implements ContainerAwareMigrationInterface { use WithPermPresetsTrait; diff --git a/migrations/Version20240606203053.php b/migrations/Version20240606203053.php index 83370ad64..1c7d2bf96 100644 --- a/migrations/Version20240606203053.php +++ b/migrations/Version20240606203053.php @@ -4,16 +4,16 @@ namespace DoctrineMigrations; +use App\Doctrine\Migration\ContainerAwareMigrationInterface; use App\Migration\AbstractMultiPlatformMigration; use App\Migration\WithPermPresetsTrait; use App\Services\UserSystem\PermissionPresetsHelper; use Doctrine\DBAL\Schema\Schema; -use Symfony\Component\DependencyInjection\ContainerAwareInterface; /** * Auto-generated Migration: Please modify to your needs! */ -final class Version20240606203053 extends AbstractMultiPlatformMigration implements ContainerAwareInterface +final class Version20240606203053 extends AbstractMultiPlatformMigration implements ContainerAwareMigrationInterface { use WithPermPresetsTrait; diff --git a/migrations/Version20250220215048.php b/migrations/Version20250220215048.php new file mode 100644 index 000000000..90a73eb10 --- /dev/null +++ b/migrations/Version20250220215048.php @@ -0,0 +1,42 @@ +addSql('ALTER TABLE attachments ADD internal_path VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE attachments ADD external_path VARCHAR(255) DEFAULT NULL'); + + //Copy the data from path to external_path and remove the path column + $this->addSql('UPDATE attachments SET external_path=path'); + $this->addSql('ALTER TABLE attachments DROP COLUMN path'); + + + $this->addSql('UPDATE attachments SET internal_path=external_path WHERE external_path LIKE \'#%MEDIA#%%\' ESCAPE \'#\''); + $this->addSql('UPDATE attachments SET internal_path=external_path WHERE external_path LIKE \'#%BASE#%%\' ESCAPE \'#\''); + $this->addSql('UPDATE attachments SET internal_path=external_path WHERE external_path LIKE \'#%SECURE#%%\' ESCAPE \'#\''); + $this->addSql('UPDATE attachments SET internal_path=external_path WHERE external_path LIKE \'#%FOOTPRINTS#%%\' ESCAPE \'#\''); + $this->addSql('UPDATE attachments SET internal_path=external_path WHERE external_path LIKE \'#%FOOTPRINTS3D#%%\' ESCAPE \'#\''); + $this->addSql('UPDATE attachments SET external_path=NULL WHERE internal_path IS NOT NULL'); + } + + public function down(Schema $schema): void + { + $this->addSql('UPDATE attachments SET external_path=internal_path WHERE internal_path IS NOT NULL'); + $this->addSql('ALTER TABLE attachments DROP COLUMN internal_path'); + $this->addSql('ALTER TABLE attachments RENAME COLUMN external_path TO path'); + } +} diff --git a/migrations/Version20250222165240.php b/migrations/Version20250222165240.php new file mode 100644 index 000000000..57cd3970d --- /dev/null +++ b/migrations/Version20250222165240.php @@ -0,0 +1,31 @@ +addSql("UPDATE attachments SET class_name = 'Part' WHERE class_name = 'PartDB\Part'"); + $this->addSql("UPDATE attachments SET class_name = 'Device' WHERE class_name = 'PartDB\Device'"); + } + + public function down(Schema $schema): void + { + //No down required, as the new format can also be read by older Part-DB version + } +} diff --git a/migrations/Version20250321075747.php b/migrations/Version20250321075747.php new file mode 100644 index 000000000..14bcb8a9d --- /dev/null +++ b/migrations/Version20250321075747.php @@ -0,0 +1,605 @@ +addSql(<<<'SQL' + CREATE TABLE part_custom_states ( + id INT AUTO_INCREMENT NOT NULL, + parent_id INT DEFAULT NULL, + id_preview_attachment INT DEFAULT NULL, + name VARCHAR(255) NOT NULL, + comment LONGTEXT NOT NULL, + not_selectable TINYINT(1) NOT NULL, + alternative_names LONGTEXT DEFAULT NULL, + last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + INDEX IDX_F552745D727ACA70 (parent_id), + INDEX IDX_F552745DEA7100A1 (id_preview_attachment), + INDEX part_custom_state_name (name), + PRIMARY KEY(id) + ) + DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE part_custom_states ADD CONSTRAINT FK_F552745D727ACA70 FOREIGN KEY (parent_id) REFERENCES part_custom_states (id) + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE part_custom_states ADD CONSTRAINT FK_F552745DEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON DELETE SET NULL + SQL); + + $this->addSql(<<<'SQL' + ALTER TABLE parts ADD id_part_custom_state INT DEFAULT NULL AFTER id_part_unit + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE parts ADD CONSTRAINT FK_6940A7FEA3ED1215 FOREIGN KEY (id_part_custom_state) REFERENCES part_custom_states (id) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_6940A7FEA3ED1215 ON parts (id_part_custom_state) + SQL); + } + + public function mySQLDown(Schema $schema): void + { + $this->addSql(<<<'SQL' + ALTER TABLE parts DROP FOREIGN KEY FK_6940A7FEA3ED1215 + SQL); + $this->addSql(<<<'SQL' + DROP INDEX IDX_6940A7FEA3ED1215 ON parts + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE parts DROP id_part_custom_state + SQL); + + $this->addSql(<<<'SQL' + ALTER TABLE part_custom_states DROP FOREIGN KEY FK_F552745D727ACA70 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE part_custom_states DROP FOREIGN KEY FK_F552745DEA7100A1 + SQL); + $this->addSql(<<<'SQL' + DROP TABLE part_custom_states + SQL); + } + + public function sqLiteUp(Schema $schema): void + { + $this->addSql(<<<'SQL' + CREATE TABLE "part_custom_states" ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + parent_id INTEGER DEFAULT NULL, + id_preview_attachment INTEGER DEFAULT NULL, + name VARCHAR(255) NOT NULL, + comment CLOB NOT NULL, + not_selectable BOOLEAN NOT NULL, + alternative_names CLOB DEFAULT NULL, + last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + CONSTRAINT FK_F552745D727ACA70 FOREIGN KEY (parent_id) REFERENCES "part_custom_states" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, + CONSTRAINT FK_F5AF83CFEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE + ) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_F552745D727ACA70 ON "part_custom_states" (parent_id) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX part_custom_state_name ON "part_custom_states" (name) + SQL); + + $this->addSql(<<<'SQL' + CREATE TEMPORARY TABLE __temp__parts AS + SELECT + id, + id_preview_attachment, + id_category, + id_footprint, + id_part_unit, + id_manufacturer, + order_orderdetails_id, + built_project_id, + datetime_added, + name, + last_modified, + needs_review, + tags, + mass, + description, + comment, + visible, + favorite, + minamount, + manufacturer_product_url, + manufacturer_product_number, + manufacturing_status, + order_quantity, + manual_order, + ipn, + provider_reference_provider_key, + provider_reference_provider_id, + provider_reference_provider_url, + provider_reference_last_updated, + eda_info_reference_prefix, + eda_info_value, + eda_info_invisible, + eda_info_exclude_from_bom, + eda_info_exclude_from_board, + eda_info_exclude_from_sim, + eda_info_kicad_symbol, + eda_info_kicad_footprint + FROM parts + SQL); + + $this->addSql(<<<'SQL' + DROP TABLE parts + SQL); + + $this->addSql(<<<'SQL' + CREATE TABLE parts ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + id_preview_attachment INTEGER DEFAULT NULL, + id_category INTEGER NOT NULL, + id_footprint INTEGER DEFAULT NULL, + id_part_unit INTEGER DEFAULT NULL, + id_manufacturer INTEGER DEFAULT NULL, + id_part_custom_state INTEGER DEFAULT NULL, + order_orderdetails_id INTEGER DEFAULT NULL, + built_project_id INTEGER DEFAULT NULL, + datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + name VARCHAR(255) NOT NULL, + last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + needs_review BOOLEAN NOT NULL, + tags CLOB NOT NULL, + mass DOUBLE PRECISION DEFAULT NULL, + description CLOB NOT NULL, + comment CLOB NOT NULL, + visible BOOLEAN NOT NULL, + favorite BOOLEAN NOT NULL, + minamount DOUBLE PRECISION NOT NULL, + manufacturer_product_url CLOB NOT NULL, + manufacturer_product_number VARCHAR(255) NOT NULL, + manufacturing_status VARCHAR(255) DEFAULT NULL, + order_quantity INTEGER NOT NULL, + manual_order BOOLEAN NOT NULL, + ipn VARCHAR(100) DEFAULT NULL, + provider_reference_provider_key VARCHAR(255) DEFAULT NULL, + provider_reference_provider_id VARCHAR(255) DEFAULT NULL, + provider_reference_provider_url VARCHAR(255) DEFAULT NULL, + provider_reference_last_updated DATETIME DEFAULT NULL, + eda_info_reference_prefix VARCHAR(255) DEFAULT NULL, + eda_info_value VARCHAR(255) DEFAULT NULL, + eda_info_invisible BOOLEAN DEFAULT NULL, + eda_info_exclude_from_bom BOOLEAN DEFAULT NULL, + eda_info_exclude_from_board BOOLEAN DEFAULT NULL, + eda_info_exclude_from_sim BOOLEAN DEFAULT NULL, + eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL, + eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL, + CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE, + CONSTRAINT FK_6940A7FE5697F554 FOREIGN KEY (id_category) REFERENCES categories (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, + CONSTRAINT FK_6940A7FE7E371A10 FOREIGN KEY (id_footprint) REFERENCES footprints (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, + CONSTRAINT FK_6940A7FE2626CEF9 FOREIGN KEY (id_part_unit) REFERENCES measurement_units (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, + CONSTRAINT FK_6940A7FE1ECB93AE FOREIGN KEY (id_manufacturer) REFERENCES manufacturers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, + CONSTRAINT FK_6940A7FEA3ED1215 FOREIGN KEY (id_part_custom_state) REFERENCES "part_custom_states" (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, + CONSTRAINT FK_6940A7FE81081E9B FOREIGN KEY (order_orderdetails_id) REFERENCES orderdetails (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, + CONSTRAINT FK_6940A7FEE8AE70D9 FOREIGN KEY (built_project_id) REFERENCES projects (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE + ) + SQL); + + $this->addSql(<<<'SQL' + INSERT INTO parts ( + id, + id_preview_attachment, + id_category, + id_footprint, + id_part_unit, + id_manufacturer, + order_orderdetails_id, + built_project_id, + datetime_added, + name, + last_modified, + needs_review, + tags, + mass, + description, + comment, + visible, + favorite, + minamount, + manufacturer_product_url, + manufacturer_product_number, + manufacturing_status, + order_quantity, + manual_order, + ipn, + provider_reference_provider_key, + provider_reference_provider_id, + provider_reference_provider_url, + provider_reference_last_updated, + eda_info_reference_prefix, + eda_info_value, + eda_info_invisible, + eda_info_exclude_from_bom, + eda_info_exclude_from_board, + eda_info_exclude_from_sim, + eda_info_kicad_symbol, + eda_info_kicad_footprint) + SELECT + id, + id_preview_attachment, + id_category, + id_footprint, + id_part_unit, + id_manufacturer, + order_orderdetails_id, + built_project_id, + datetime_added, + name, + last_modified, + needs_review, + tags, + mass, + description, + comment, + visible, + favorite, + minamount, + manufacturer_product_url, + manufacturer_product_number, + manufacturing_status, + order_quantity, + manual_order, + ipn, + provider_reference_provider_key, + provider_reference_provider_id, + provider_reference_provider_url, + provider_reference_last_updated, + eda_info_reference_prefix, + eda_info_value, + eda_info_invisible, + eda_info_exclude_from_bom, + eda_info_exclude_from_board, + eda_info_exclude_from_sim, + eda_info_kicad_symbol, + eda_info_kicad_footprint + FROM __temp__parts + SQL); + + $this->addSql(<<<'SQL' + DROP TABLE __temp__parts + SQL); + + $this->addSql(<<<'SQL' + CREATE INDEX parts_idx_name ON parts (name) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX parts_idx_ipn ON parts (ipn) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX parts_idx_datet_name_last_id_needs ON parts (datetime_added, name, last_modified, id, needs_review) + SQL); + $this->addSql(<<<'SQL' + CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON parts (built_project_id) + SQL); + $this->addSql(<<<'SQL' + CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON parts (order_orderdetails_id) + SQL); + $this->addSql(<<<'SQL' + CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON parts (ipn) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_6940A7FEEA7100A1 ON parts (id_preview_attachment) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_6940A7FE7E371A10 ON parts (id_footprint) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_6940A7FE5697F554 ON parts (id_category) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_6940A7FE2626CEF9 ON parts (id_part_unit) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_6940A7FE1ECB93AE ON parts (id_manufacturer) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_6940A7FEA3ED1215 ON parts (id_part_custom_state) + SQL); + } + + public function sqLiteDown(Schema $schema): void + { + $this->addSql(<<<'SQL' + CREATE TEMPORARY TABLE __temp__parts AS + SELECT + id, + id_preview_attachment, + id_category, + id_footprint, + id_part_unit, + id_manufacturer, + order_orderdetails_id, + built_project_id, + datetime_added, + name, + last_modified, + needs_review, + tags, + mass, + description, + comment, + visible, + favorite, + minamount, + manufacturer_product_url, + manufacturer_product_number, + manufacturing_status, + order_quantity, + manual_order, + ipn, + provider_reference_provider_key, + provider_reference_provider_id, + provider_reference_provider_url, + provider_reference_last_updated, + eda_info_reference_prefix, + eda_info_value, + eda_info_invisible, + eda_info_exclude_from_bom, + eda_info_exclude_from_board, + eda_info_exclude_from_sim, + eda_info_kicad_symbol, + eda_info_kicad_footprint + FROM "parts" + SQL); + $this->addSql(<<<'SQL' + DROP TABLE "parts" + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE "parts" ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + id_preview_attachment INTEGER DEFAULT NULL, + id_category INTEGER NOT NULL, + id_footprint INTEGER DEFAULT NULL, + id_part_unit INTEGER DEFAULT NULL, + id_manufacturer INTEGER DEFAULT NULL, + order_orderdetails_id INTEGER DEFAULT NULL, + built_project_id INTEGER DEFAULT NULL, + datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + name VARCHAR(255) NOT NULL, + last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + needs_review BOOLEAN NOT NULL, + tags CLOB NOT NULL, + mass DOUBLE PRECISION DEFAULT NULL, + description CLOB NOT NULL, + comment CLOB NOT NULL, + visible BOOLEAN NOT NULL, + favorite BOOLEAN NOT NULL, + minamount DOUBLE PRECISION NOT NULL, + manufacturer_product_url CLOB NOT NULL, + manufacturer_product_number VARCHAR(255) NOT NULL, + manufacturing_status VARCHAR(255) DEFAULT NULL, + order_quantity INTEGER NOT NULL, + manual_order BOOLEAN NOT NULL, + ipn VARCHAR(100) DEFAULT NULL, + provider_reference_provider_key VARCHAR(255) DEFAULT NULL, + provider_reference_provider_id VARCHAR(255) DEFAULT NULL, + provider_reference_provider_url VARCHAR(255) DEFAULT NULL, + provider_reference_last_updated DATETIME DEFAULT NULL, + eda_info_reference_prefix VARCHAR(255) DEFAULT NULL, + eda_info_value VARCHAR(255) DEFAULT NULL, + eda_info_invisible BOOLEAN DEFAULT NULL, + eda_info_exclude_from_bom BOOLEAN DEFAULT NULL, + eda_info_exclude_from_board BOOLEAN DEFAULT NULL, + eda_info_exclude_from_sim BOOLEAN DEFAULT NULL, + eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL, + eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL, + CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE, + CONSTRAINT FK_6940A7FE5697F554 FOREIGN KEY (id_category) REFERENCES "categories" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, + CONSTRAINT FK_6940A7FE7E371A10 FOREIGN KEY (id_footprint) REFERENCES "footprints" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, + CONSTRAINT FK_6940A7FE2626CEF9 FOREIGN KEY (id_part_unit) REFERENCES "measurement_units" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, + CONSTRAINT FK_6940A7FE1ECB93AE FOREIGN KEY (id_manufacturer) REFERENCES "manufacturers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, + CONSTRAINT FK_6940A7FE81081E9B FOREIGN KEY (order_orderdetails_id) REFERENCES "orderdetails" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, + CONSTRAINT FK_6940A7FEE8AE70D9 FOREIGN KEY (built_project_id) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE + ) + SQL); + $this->addSql(<<<'SQL' + INSERT INTO "parts" ( + id, + id_preview_attachment, + id_category, + id_footprint, + id_part_unit, + id_manufacturer, + order_orderdetails_id, + built_project_id, + datetime_added, + name, + last_modified, + needs_review, + tags, + mass, + description, + comment, + visible, + favorite, + minamount, + manufacturer_product_url, + manufacturer_product_number, + manufacturing_status, + order_quantity, + manual_order, + ipn, + provider_reference_provider_key, + provider_reference_provider_id, + provider_reference_provider_url, + provider_reference_last_updated, + eda_info_reference_prefix, + eda_info_value, + eda_info_invisible, + eda_info_exclude_from_bom, + eda_info_exclude_from_board, + eda_info_exclude_from_sim, + eda_info_kicad_symbol, + eda_info_kicad_footprint + ) SELECT + id, + id_preview_attachment, + id_category, + id_footprint, + id_part_unit, + id_manufacturer, + order_orderdetails_id, + built_project_id, + datetime_added, + name, + last_modified, + needs_review, + tags, + mass, + description, + comment, + visible, + favorite, + minamount, + manufacturer_product_url, + manufacturer_product_number, + manufacturing_status, + order_quantity, + manual_order, + ipn, + provider_reference_provider_key, + provider_reference_provider_id, + provider_reference_provider_url, + provider_reference_last_updated, + eda_info_reference_prefix, + eda_info_value, + eda_info_invisible, + eda_info_exclude_from_bom, + eda_info_exclude_from_board, + eda_info_exclude_from_sim, + eda_info_kicad_symbol, + eda_info_kicad_footprint + FROM __temp__parts + SQL); + + $this->addSql(<<<'SQL' + DROP TABLE __temp__parts + SQL); + $this->addSql(<<<'SQL' + CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON "parts" (ipn) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_6940A7FEEA7100A1 ON "parts" (id_preview_attachment) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_6940A7FE5697F554 ON "parts" (id_category) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_6940A7FE7E371A10 ON "parts" (id_footprint) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_6940A7FE2626CEF9 ON "parts" (id_part_unit) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_6940A7FE1ECB93AE ON "parts" (id_manufacturer) + SQL); + $this->addSql(<<<'SQL' + CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON "parts" (order_orderdetails_id) + SQL); + $this->addSql(<<<'SQL' + CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON "parts" (built_project_id) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX parts_idx_datet_name_last_id_needs ON "parts" (datetime_added, name, last_modified, id, needs_review) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX parts_idx_name ON "parts" (name) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX parts_idx_ipn ON "parts" (ipn) + SQL); + + $this->addSql(<<<'SQL' + DROP TABLE "part_custom_states" + SQL); + } + + public function postgreSQLUp(Schema $schema): void + { + $this->addSql(<<<'SQL' + CREATE TABLE "part_custom_states" ( + id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + parent_id INT DEFAULT NULL, + id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id), + name VARCHAR(255) NOT NULL, + comment TEXT NOT NULL, + not_selectable BOOLEAN NOT NULL, + alternative_names TEXT DEFAULT NULL, + last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL + ) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_F552745D727ACA70 ON "part_custom_states" (parent_id) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_F552745DEA7100A1 ON "part_custom_states" (id_preview_attachment) + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE "part_custom_states" + ADD CONSTRAINT FK_F552745D727ACA70 + FOREIGN KEY (parent_id) REFERENCES "part_custom_states" (id) NOT DEFERRABLE INITIALLY IMMEDIATE + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE "part_custom_states" + ADD CONSTRAINT FK_F552745DEA7100A1 + FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE + SQL); + + + $this->addSql(<<<'SQL' + ALTER TABLE parts ADD id_part_custom_state INT DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE parts ADD CONSTRAINT FK_6940A7FEA3ED1215 FOREIGN KEY (id_part_custom_state) REFERENCES "part_custom_states" (id) NOT DEFERRABLE INITIALLY IMMEDIATE + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_6940A7FEA3ED1215 ON parts (id_part_custom_state) + SQL); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->addSql(<<<'SQL' + ALTER TABLE "parts" DROP CONSTRAINT FK_6940A7FEA3ED1215 + SQL); + $this->addSql(<<<'SQL' + DROP INDEX IDX_6940A7FEA3ED1215 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE "parts" DROP id_part_custom_state + SQL); + + $this->addSql(<<<'SQL' + ALTER TABLE "part_custom_states" DROP CONSTRAINT FK_F552745D727ACA70 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE "part_custom_states" DROP CONSTRAINT FK_F552745DEA7100A1 + SQL); + $this->addSql(<<<'SQL' + DROP TABLE "part_custom_states" + SQL); + } +} diff --git a/migrations/Version20250706201121.php b/migrations/Version20250706201121.php new file mode 100644 index 000000000..b75639783 --- /dev/null +++ b/migrations/Version20250706201121.php @@ -0,0 +1,49 @@ +addSql('CREATE TABLE settings_entry (`key` VARCHAR(255) NOT NULL, `data` JSON DEFAULT NULL, id INT AUTO_INCREMENT NOT NULL, UNIQUE INDEX UNIQ_93F8DB394E645A7E (`key`), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci`'); + + } + + public function mySQLDown(Schema $schema): void + { + $this->addSql('DROP TABLE settings_entry'); + } + + public function sqLiteUp(Schema $schema): void + { + $this->addSql('CREATE TABLE settings_entry ("key" VARCHAR(255) NOT NULL, "data" CLOB DEFAULT NULL, id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_93F8DB39F48571EB ON settings_entry ("key")'); + } + + public function sqLiteDown(Schema $schema): void + { + $this->addSql('DROP TABLE settings_entry'); + } + + public function postgreSQLUp(Schema $schema): void + { + $this->addSql('CREATE TABLE settings_entry ("key" VARCHAR(255) NOT NULL, "data" JSON DEFAULT NULL, id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_93F8DB39F48571EB ON settings_entry ("key")'); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->addSql('DROP TABLE settings_entry'); + } +} diff --git a/migrations/Version20250802205143.php b/migrations/Version20250802205143.php new file mode 100644 index 000000000..5eb09a77b --- /dev/null +++ b/migrations/Version20250802205143.php @@ -0,0 +1,70 @@ +addSql('CREATE TABLE bulk_info_provider_import_jobs (id INT AUTO_INCREMENT NOT NULL, name LONGTEXT NOT NULL, field_mappings LONGTEXT NOT NULL, search_results LONGTEXT NOT NULL, status VARCHAR(20) NOT NULL, created_at DATETIME NOT NULL, completed_at DATETIME DEFAULT NULL, prefetch_details TINYINT(1) NOT NULL, created_by_id INT NOT NULL, CONSTRAINT FK_7F58C1EDB03A8386 FOREIGN KEY (created_by_id) REFERENCES `users` (id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB'); + $this->addSql('CREATE INDEX IDX_7F58C1EDB03A8386 ON bulk_info_provider_import_jobs (created_by_id)'); + + $this->addSql('CREATE TABLE bulk_info_provider_import_job_parts (id INT AUTO_INCREMENT NOT NULL, status VARCHAR(20) NOT NULL, reason LONGTEXT DEFAULT NULL, completed_at DATETIME DEFAULT NULL, job_id INT NOT NULL, part_id INT NOT NULL, CONSTRAINT FK_CD93F28FBE04EA9 FOREIGN KEY (job_id) REFERENCES bulk_info_provider_import_jobs (id), CONSTRAINT FK_CD93F28F4CE34BEC FOREIGN KEY (part_id) REFERENCES `parts` (id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB'); + $this->addSql('CREATE INDEX IDX_CD93F28FBE04EA9 ON bulk_info_provider_import_job_parts (job_id)'); + $this->addSql('CREATE INDEX IDX_CD93F28F4CE34BEC ON bulk_info_provider_import_job_parts (part_id)'); + $this->addSql('CREATE UNIQUE INDEX unique_job_part ON bulk_info_provider_import_job_parts (job_id, part_id)'); + } + + public function mySQLDown(Schema $schema): void + { + $this->addSql('DROP TABLE bulk_info_provider_import_job_parts'); + $this->addSql('DROP TABLE bulk_info_provider_import_jobs'); + } + + public function sqLiteUp(Schema $schema): void + { + $this->addSql('CREATE TABLE bulk_info_provider_import_jobs (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name CLOB NOT NULL, field_mappings CLOB NOT NULL, search_results CLOB NOT NULL, status VARCHAR(20) NOT NULL, created_at DATETIME NOT NULL, completed_at DATETIME DEFAULT NULL, prefetch_details BOOLEAN NOT NULL, created_by_id INTEGER NOT NULL, CONSTRAINT FK_7F58C1EDB03A8386 FOREIGN KEY (created_by_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('CREATE INDEX IDX_7F58C1EDB03A8386 ON bulk_info_provider_import_jobs (created_by_id)'); + + $this->addSql('CREATE TABLE bulk_info_provider_import_job_parts (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, status VARCHAR(20) NOT NULL, reason CLOB DEFAULT NULL, completed_at DATETIME DEFAULT NULL, job_id INTEGER NOT NULL, part_id INTEGER NOT NULL, CONSTRAINT FK_CD93F28FBE04EA9 FOREIGN KEY (job_id) REFERENCES bulk_info_provider_import_jobs (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_CD93F28F4CE34BEC FOREIGN KEY (part_id) REFERENCES "parts" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('CREATE INDEX IDX_CD93F28FBE04EA9 ON bulk_info_provider_import_job_parts (job_id)'); + $this->addSql('CREATE INDEX IDX_CD93F28F4CE34BEC ON bulk_info_provider_import_job_parts (part_id)'); + $this->addSql('CREATE UNIQUE INDEX unique_job_part ON bulk_info_provider_import_job_parts (job_id, part_id)'); + } + + public function sqLiteDown(Schema $schema): void + { + $this->addSql('DROP TABLE bulk_info_provider_import_job_parts'); + $this->addSql('DROP TABLE bulk_info_provider_import_jobs'); + } + + public function postgreSQLUp(Schema $schema): void + { + $this->addSql('CREATE TABLE bulk_info_provider_import_jobs (id SERIAL PRIMARY KEY NOT NULL, name TEXT NOT NULL, field_mappings TEXT NOT NULL, search_results TEXT NOT NULL, status VARCHAR(20) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, completed_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, prefetch_details BOOLEAN NOT NULL, created_by_id INT NOT NULL, CONSTRAINT FK_7F58C1EDB03A8386 FOREIGN KEY (created_by_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('CREATE INDEX IDX_7F58C1EDB03A8386 ON bulk_info_provider_import_jobs (created_by_id)'); + + $this->addSql('CREATE TABLE bulk_info_provider_import_job_parts (id SERIAL PRIMARY KEY NOT NULL, status VARCHAR(20) NOT NULL, reason TEXT DEFAULT NULL, completed_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, job_id INT NOT NULL, part_id INT NOT NULL, CONSTRAINT FK_CD93F28FBE04EA9 FOREIGN KEY (job_id) REFERENCES bulk_info_provider_import_jobs (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_CD93F28F4CE34BEC FOREIGN KEY (part_id) REFERENCES parts (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('CREATE INDEX IDX_CD93F28FBE04EA9 ON bulk_info_provider_import_job_parts (job_id)'); + $this->addSql('CREATE INDEX IDX_CD93F28F4CE34BEC ON bulk_info_provider_import_job_parts (part_id)'); + $this->addSql('CREATE UNIQUE INDEX unique_job_part ON bulk_info_provider_import_job_parts (job_id, part_id)'); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->addSql('DROP TABLE bulk_info_provider_import_job_parts'); + $this->addSql('DROP TABLE bulk_info_provider_import_jobs'); + } +} diff --git a/migrations/Version20250813214628.php b/migrations/Version20250813214628.php new file mode 100644 index 000000000..5b9350b2e --- /dev/null +++ b/migrations/Version20250813214628.php @@ -0,0 +1,75 @@ +connection; + $rows = $connection->fetchAllAssociative('SELECT id, transports, other_ui FROM webauthn_keys'); + + foreach ($rows as $row) { + $id = $row['id']; + $new_transports = json_encode(unserialize($row['transports'], ['allowed_classes' => false]), + JSON_THROW_ON_ERROR); + $new_other_ui = json_encode(unserialize($row['other_ui'], ['allowed_classes' => false]), + JSON_THROW_ON_ERROR); + + $connection->executeStatement( + 'UPDATE webauthn_keys SET transports = :transports, other_ui = :other_ui WHERE id = :id', + [ + 'transports' => $new_transports, + 'other_ui' => $new_other_ui, + 'id' => $id, + ] + ); + } + } + + public function mySQLUp(Schema $schema): void + { + $this->convertArrayToJson(); + $this->addSql('ALTER TABLE webauthn_keys CHANGE transports transports JSON NOT NULL, CHANGE other_ui other_ui JSON DEFAULT NULL'); + } + + public function mySQLDown(Schema $schema): void + { + $this->addSql('ALTER TABLE webauthn_keys CHANGE transports transports LONGTEXT NOT NULL, CHANGE other_ui other_ui LONGTEXT DEFAULT NULL'); + } + + public function sqLiteUp(Schema $schema): void + { + //As there is no JSON type in SQLite, we only need to convert the data. + $this->convertArrayToJson(); + } + + public function sqLiteDown(Schema $schema): void + { + //Nothing to do here, as SQLite does not support JSON type and we are not changing the column type. + } + + public function postgreSQLUp(Schema $schema): void + { + $this->convertArrayToJson(); + $this->addSql('ALTER TABLE webauthn_keys ALTER transports TYPE JSON USING transports::JSON'); + $this->addSql('ALTER TABLE webauthn_keys ALTER other_ui TYPE JSON USING other_ui::JSON'); + } + + public function postgreSQLDown(Schema $schema): void + { + $this->addSql('ALTER TABLE webauthn_keys ALTER transports TYPE TEXT'); + $this->addSql('ALTER TABLE webauthn_keys ALTER other_ui TYPE TEXT'); + } +} diff --git a/package.json b/package.json index 38656c728..f2fbebcd0 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,11 @@ "devDependencies": { "@babel/core": "^7.19.6", "@babel/preset-env": "^7.19.4", - "@fortawesome/fontawesome-free": "^6.1.1", + "@fortawesome/fontawesome-free": "^7.0.0", "@hotwired/stimulus": "^3.0.0", "@hotwired/turbo": "^8.0.1", "@popperjs/core": "^2.10.2", - "@symfony/stimulus-bridge": "^3.2.0", + "@symfony/stimulus-bridge": "^4.0.0", "@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets", "@symfony/ux-turbo": "file:vendor/symfony/ux-turbo/assets", "@symfony/webpack-encore": "^5.0.0", @@ -29,54 +29,28 @@ "watch": "encore dev --watch", "build": "encore production --progress" }, + "engines": { + "node": ">=20.0.0" + }, "dependencies": { "@algolia/autocomplete-js": "^1.17.0", "@algolia/autocomplete-plugin-recent-searches": "^1.17.0", "@algolia/autocomplete-theme-classic": "^1.17.0", - "@ckeditor/ckeditor5-alignment": "^44.0.0", - "@ckeditor/ckeditor5-autoformat": "^44.0.0", - "@ckeditor/ckeditor5-basic-styles": "^44.0.0", - "@ckeditor/ckeditor5-block-quote": "^44.0.0", - "@ckeditor/ckeditor5-code-block": "^44.0.0", "@ckeditor/ckeditor5-dev-translations": "^43.0.1", "@ckeditor/ckeditor5-dev-utils": "^43.0.1", - "@ckeditor/ckeditor5-editor-classic": "^44.0.0", - "@ckeditor/ckeditor5-essentials": "^44.0.0", - "@ckeditor/ckeditor5-find-and-replace": "^44.0.0", - "@ckeditor/ckeditor5-font": "^44.0.0", - "@ckeditor/ckeditor5-heading": "^44.0.0", - "@ckeditor/ckeditor5-highlight": "^44.0.0", - "@ckeditor/ckeditor5-horizontal-line": "^44.0.0", - "@ckeditor/ckeditor5-html-embed": "^44.0.0", - "@ckeditor/ckeditor5-html-support": "^44.0.0", - "@ckeditor/ckeditor5-image": "^44.0.0", - "@ckeditor/ckeditor5-indent": "^44.0.0", - "@ckeditor/ckeditor5-link": "^44.0.0", - "@ckeditor/ckeditor5-list": "^44.0.0", - "@ckeditor/ckeditor5-markdown-gfm": "^44.0.0", - "@ckeditor/ckeditor5-media-embed": "^44.0.0", - "@ckeditor/ckeditor5-paragraph": "^44.0.0", - "@ckeditor/ckeditor5-paste-from-office": "^44.0.0", - "@ckeditor/ckeditor5-remove-format": "^44.0.0", - "@ckeditor/ckeditor5-source-editing": "^44.0.0", - "@ckeditor/ckeditor5-special-characters": "^44.0.0", - "@ckeditor/ckeditor5-table": "^44.0.0", - "@ckeditor/ckeditor5-theme-lark": "^44.0.0", - "@ckeditor/ckeditor5-upload": "^44.0.0", - "@ckeditor/ckeditor5-watchdog": "^44.0.0", - "@ckeditor/ckeditor5-word-count": "^44.0.0", "@jbtronics/bs-treeview": "^1.0.1", - "@part-db/html5-qrcode": "^3.1.0", + "@part-db/html5-qrcode": "^4.0.0", "@zxcvbn-ts/core": "^3.0.2", "@zxcvbn-ts/language-common": "^3.0.3", "@zxcvbn-ts/language-de": "^3.0.1", "@zxcvbn-ts/language-en": "^3.0.1", "@zxcvbn-ts/language-fr": "^3.0.1", "@zxcvbn-ts/language-ja": "^3.0.1", - "barcode-detector": "^2.3.1", + "barcode-detector": "^3.0.5", "bootbox": "^6.0.0", "bootswatch": "^5.1.3", "bs-custom-file-input": "^1.3.4", + "ckeditor5": "^47.0.0", "clipboard": "^2.0.4", "compression-webpack-plugin": "^11.1.0", "datatables.net": "^2.0.0", @@ -85,14 +59,13 @@ "datatables.net-colreorder-bs5": "^2.0.0", "datatables.net-fixedheader-bs5": "^4.0.0", "datatables.net-responsive-bs5": "^3.0.0", - "datatables.net-select-bs5": "^2.0.0", + "datatables.net-select-bs5": "^3.0.1", "dompurify": "^3.0.3", - "emoji.json": "^15.0.0", "exports-loader": "^5.0.0", "json-formatter-js": "^2.3.4", "jszip": "^3.2.0", "katex": "^0.16.0", - "marked": "^15.0.4", + "marked": "^16.1.1", "marked-gfm-heading-id": "^4.1.1", "marked-mangle": "^1.0.1", "pdfmake": "^0.2.2", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 7ee7596f0..3feb49406 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,36 +1,42 @@ - - - - - - - - - - - - src - - + + + + + + + + + + + + + src + + - - - tests - - - - - - - - + + + tests + + + + + + + + + diff --git a/public/.htaccess b/public/.htaccess index ee3b54505..a13baeeea 100644 --- a/public/.htaccess +++ b/public/.htaccess @@ -86,7 +86,7 @@ DirectoryIndex index.php # - use Apache >= 2.3.9 and replace all L flags by END flags and remove the # following RewriteCond (best solution) RewriteCond %{ENV:REDIRECT_STATUS} ="" - RewriteRule ^index\.php(?:/(.*)|$) %{ENV:BASE}/$1 [R=301,L] + RewriteRule ^index\.php(?:/(.*)|$) %{ENV:BASE}/$1 [R=308,L] # If the requested filename exists, simply serve it. # We only want to let Apache serve files and not directories. @@ -118,3 +118,10 @@ DirectoryIndex index.php # RedirectTemp cannot be used instead + +# Set Content-Security-Policy for svg files (and compressed variants), to block embedded javascript in there + + + Header set Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none';" + + \ No newline at end of file diff --git a/public/img/calculator/ratio.png b/public/img/calculator/ratio.png deleted file mode 100644 index d6decff35..000000000 Binary files a/public/img/calculator/ratio.png and /dev/null differ diff --git a/public/img/calculator/v1.png b/public/img/calculator/v1.png deleted file mode 100644 index c98d3ad43..000000000 Binary files a/public/img/calculator/v1.png and /dev/null differ diff --git a/public/img/calculator/v2.png b/public/img/calculator/v2.png deleted file mode 100644 index 081386fe3..000000000 Binary files a/public/img/calculator/v2.png and /dev/null differ diff --git a/public/img/labels/100.png b/public/img/labels/100.png deleted file mode 100644 index f68a23a9d..000000000 Binary files a/public/img/labels/100.png and /dev/null differ diff --git a/public/img/labels/1001.png b/public/img/labels/1001.png deleted file mode 100644 index c87e4ceb7..000000000 Binary files a/public/img/labels/1001.png and /dev/null differ diff --git a/public/img/labels/1002.png b/public/img/labels/1002.png deleted file mode 100644 index 68b6594c8..000000000 Binary files a/public/img/labels/1002.png and /dev/null differ diff --git a/public/img/labels/1003.png b/public/img/labels/1003.png deleted file mode 100644 index 2abbd616f..000000000 Binary files a/public/img/labels/1003.png and /dev/null differ diff --git a/public/img/labels/100R.png b/public/img/labels/100R.png deleted file mode 100644 index 34fb8fa89..000000000 Binary files a/public/img/labels/100R.png and /dev/null differ diff --git a/public/img/labels/101.png b/public/img/labels/101.png deleted file mode 100644 index dd07aa392..000000000 Binary files a/public/img/labels/101.png and /dev/null differ diff --git a/public/img/labels/102.png b/public/img/labels/102.png deleted file mode 100644 index a54e16b77..000000000 Binary files a/public/img/labels/102.png and /dev/null differ diff --git a/public/img/labels/10R2.png b/public/img/labels/10R2.png deleted file mode 100644 index 2b57f7d4a..000000000 Binary files a/public/img/labels/10R2.png and /dev/null differ diff --git a/public/img/labels/220.png b/public/img/labels/220.png deleted file mode 100644 index 28ede43d4..000000000 Binary files a/public/img/labels/220.png and /dev/null differ diff --git a/public/img/labels/221K.png b/public/img/labels/221K.png deleted file mode 100644 index 1dbb0c615..000000000 Binary files a/public/img/labels/221K.png and /dev/null differ diff --git a/public/img/labels/246-20.png b/public/img/labels/246-20.png deleted file mode 100644 index 590f7c5df..000000000 Binary files a/public/img/labels/246-20.png and /dev/null differ diff --git a/public/img/labels/3F3.png b/public/img/labels/3F3.png deleted file mode 100644 index ce85ae97f..000000000 Binary files a/public/img/labels/3F3.png and /dev/null differ diff --git a/public/img/labels/R10.png b/public/img/labels/R10.png deleted file mode 100644 index 60a901822..000000000 Binary files a/public/img/labels/R10.png and /dev/null differ diff --git a/public/img/labels/template-c-elko-alu.png b/public/img/labels/template-c-elko-alu.png deleted file mode 100644 index 24d68d914..000000000 Binary files a/public/img/labels/template-c-elko-alu.png and /dev/null differ diff --git a/public/img/labels/template-c-elko.png b/public/img/labels/template-c-elko.png deleted file mode 100644 index 97e3c1ef8..000000000 Binary files a/public/img/labels/template-c-elko.png and /dev/null differ diff --git a/public/img/labels/template-c-tantal.png b/public/img/labels/template-c-tantal.png deleted file mode 100644 index 3e49efeef..000000000 Binary files a/public/img/labels/template-c-tantal.png and /dev/null differ diff --git a/public/img/labels/template-l.png b/public/img/labels/template-l.png deleted file mode 100644 index 7e5afd925..000000000 Binary files a/public/img/labels/template-l.png and /dev/null differ diff --git a/public/img/labels/template-r.png b/public/img/labels/template-r.png deleted file mode 100644 index 554d2a08f..000000000 Binary files a/public/img/labels/template-r.png and /dev/null differ diff --git a/public/img/partdb/alldatasheet.png b/public/img/partdb/alldatasheet.png deleted file mode 100644 index d7c1d40f3..000000000 Binary files a/public/img/partdb/alldatasheet.png and /dev/null differ diff --git a/public/img/partdb/dc.png b/public/img/partdb/dc.png deleted file mode 100644 index 4a9403af6..000000000 Binary files a/public/img/partdb/dc.png and /dev/null differ diff --git a/public/img/partdb/dummytn.png b/public/img/partdb/dummytn.png deleted file mode 100644 index e63c92487..000000000 Binary files a/public/img/partdb/dummytn.png and /dev/null differ diff --git a/public/img/partdb/favicon.ico b/public/img/partdb/favicon.ico deleted file mode 100644 index 1d8387940..000000000 Binary files a/public/img/partdb/favicon.ico and /dev/null differ diff --git a/public/img/partdb/file_all.svg b/public/img/partdb/file_all.svg deleted file mode 100644 index bb4b42482..000000000 --- a/public/img/partdb/file_all.svg +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/img/partdb/file_dc.svg b/public/img/partdb/file_dc.svg deleted file mode 100644 index f0039881e..000000000 --- a/public/img/partdb/file_dc.svg +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - image/svg+xml - - - - - - - - - - - DC - diff --git a/public/img/partdb/file_google.svg b/public/img/partdb/file_google.svg deleted file mode 100644 index 20ea96bf7..000000000 --- a/public/img/partdb/file_google.svg +++ /dev/null @@ -1,5 +0,0 @@ - - -google - - diff --git a/public/img/partdb/file_octo.svg b/public/img/partdb/file_octo.svg deleted file mode 100644 index 307439a59..000000000 --- a/public/img/partdb/file_octo.svg +++ /dev/null @@ -1,5 +0,0 @@ - - -cog - - diff --git a/public/img/partdb/file_reichelt.svg b/public/img/partdb/file_reichelt.svg deleted file mode 100644 index 488dafaa8..000000000 --- a/public/img/partdb/file_reichelt.svg +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - image/svg+xml - - - - - - - - - - - - - - diff --git a/public/img/partdb/help.png b/public/img/partdb/help.png deleted file mode 100644 index 7cb049786..000000000 Binary files a/public/img/partdb/help.png and /dev/null differ diff --git a/public/img/partdb/partdb.png b/public/img/partdb/partdb.png deleted file mode 100644 index 53f51afb8..000000000 Binary files a/public/img/partdb/partdb.png and /dev/null differ diff --git a/public/img/partdb/reichelt.png b/public/img/partdb/reichelt.png deleted file mode 100644 index fcfcfd49c..000000000 Binary files a/public/img/partdb/reichelt.png and /dev/null differ diff --git a/public/img/partdb/template-pdf.png b/public/img/partdb/template-pdf.png deleted file mode 100644 index 211bf5a49..000000000 Binary files a/public/img/partdb/template-pdf.png and /dev/null differ diff --git a/public/kicad/footprints.txt b/public/kicad/footprints.txt index 0f65227e0..8f0f944c7 100644 --- a/public/kicad/footprints.txt +++ b/public/kicad/footprints.txt @@ -1,6 +1,6 @@ # This file contains all the KiCad footprints available in the official library # Generated by footprints.sh -# on Sat Dec 2 19:52:08 CET 2023 +# on Sun Feb 16 21:19:56 CET 2025 Audio_Module:Reverb_BTDR-1H Audio_Module:Reverb_BTDR-1V Battery:BatteryClip_Keystone_54_D16-19mm @@ -38,6 +38,8 @@ Battery:BatteryHolder_MPD_BC2003_1x2032 Battery:BatteryHolder_MPD_BC2AAPC_2xAA Battery:BatteryHolder_MPD_BH-18650-PC2 Battery:BatteryHolder_Multicomp_BC-2001_1x2032 +Battery:BatteryHolder_MYOUNG_BS-07-A1BJ001_CR2032 +Battery:BatteryHolder_Renata_SMTU2032-LF_1x2032 Battery:BatteryHolder_Seiko_MS621F Battery:BatteryHolder_TruPower_BH-331P_3xAA Battery:Battery_CR1225 @@ -45,6 +47,7 @@ Battery:Battery_Panasonic_CR1025-VSK_Vertical_CircularHoles Battery:Battery_Panasonic_CR1220-VCN_Vertical_CircularHoles Battery:Battery_Panasonic_CR1632-V1AN_Vertical_CircularHoles Battery:Battery_Panasonic_CR2025-V1AK_Vertical_CircularHoles +Battery:Battery_Panasonic_CR2032-HFN_Horizontal_CircularHoles Battery:Battery_Panasonic_CR2032-VS1N_Vertical_CircularHoles Battery:Battery_Panasonic_CR2354-VCN_Vertical_CircularHoles Battery:Battery_Panasonic_CR2450-VAN_Vertical_CircularHoles @@ -177,16 +180,16 @@ Button_Switch_SMD:SW_DPDT_CK_JS202011JCQN Button_Switch_SMD:SW_MEC_5GSH9 Button_Switch_SMD:SW_Push_1P1T-MP_NO_Horizontal_Alps_SKRTLAE010 Button_Switch_SMD:SW_Push_1P1T-SH_NO_CK_KMR2xxG -Button_Switch_SMD:SW_Push_1P1T_NO_6x6mm_H9.5mm Button_Switch_SMD:SW_Push_1P1T_NO_CK_KMR2 Button_Switch_SMD:SW_Push_1P1T_NO_CK_KSC6xxJ Button_Switch_SMD:SW_Push_1P1T_NO_CK_KSC7xxJ Button_Switch_SMD:SW_Push_1P1T_NO_CK_PTS125Sx43PSMTR Button_Switch_SMD:SW_Push_1P1T_NO_Vertical_Wuerth_434133025816 Button_Switch_SMD:SW_Push_1P1T_XKB_TS-1187A +Button_Switch_SMD:SW_Push_1TS009xxxx-xxxx-xxxx_6x6x5mm Button_Switch_SMD:SW_Push_SPST_NO_Alps_SKRK Button_Switch_SMD:SW_SP3T_PCM13 -Button_Switch_SMD:SW_SPDT_CK-JS102011SAQN +Button_Switch_SMD:SW_SPDT_CK_JS102011SAQN Button_Switch_SMD:SW_SPDT_PCM12 Button_Switch_SMD:SW_SPDT_REED_MSDM-DT Button_Switch_SMD:SW_SPST_B3S-1000 @@ -219,6 +222,9 @@ Button_Switch_SMD:SW_SPST_Omron_B3FS-105xP Button_Switch_SMD:SW_SPST_Panasonic_EVQPL_3PL_5PL_PT_A08 Button_Switch_SMD:SW_SPST_Panasonic_EVQPL_3PL_5PL_PT_A15 Button_Switch_SMD:SW_SPST_PTS645 +Button_Switch_SMD:SW_SPST_PTS647_Sx38 +Button_Switch_SMD:SW_SPST_PTS647_Sx50 +Button_Switch_SMD:SW_SPST_PTS647_Sx70 Button_Switch_SMD:SW_SPST_PTS810 Button_Switch_SMD:SW_SPST_REED_CT05-XXXX-G1 Button_Switch_SMD:SW_SPST_REED_CT05-XXXX-J1 @@ -235,9 +241,8 @@ Button_Switch_SMD:SW_Tactile_SPST_NO_Straight_CK_PTS636Sx25SMTRLFS Button_Switch_THT:KSA_Tactile_SPST Button_Switch_THT:Nidec_Copal_SH-7010C Button_Switch_THT:Push_E-Switch_KS01Q01 -Button_Switch_THT:SW_CuK_JS202011AQN_DPDT_Angled -Button_Switch_THT:SW_CuK_JS202011CQN_DPDT_Straight -Button_Switch_THT:SW_CuK_OS102011MA1QN1_SPDT_Angled +Button_Switch_THT:SW_CK_JS202011AQN_DPDT_Angled +Button_Switch_THT:SW_CK_JS202011CQN_DPDT_Straight Button_Switch_THT:SW_CW_GPTS203211B Button_Switch_THT:SW_DIP_SPSTx01_Piano_10.8x4.1mm_W7.62mm_P2.54mm Button_Switch_THT:SW_DIP_SPSTx01_Slide_6.7x4.1mm_W7.62mm_P2.54mm_LowProfile @@ -322,8 +327,12 @@ Button_Switch_THT:SW_PUSH_6mm_H9.5mm Button_Switch_THT:SW_PUSH_E-Switch_FS5700DP_DPDT Button_Switch_THT:SW_PUSH_LCD_E3_SAxxxx Button_Switch_THT:SW_PUSH_LCD_E3_SAxxxx_SocketPins -Button_Switch_THT:SW_Slide_1P2T_CK_OS102011MS2Q +Button_Switch_THT:SW_Slide-03_Wuerth-WS-SLTV_10x2.5x6.4_P2.54mm +Button_Switch_THT:SW_Slide_SPDT_Angled_CK_OS102011MA1Q +Button_Switch_THT:SW_Slide_SPDT_Straight_CK_OS102011MS2Q Button_Switch_THT:SW_SPST_Omron_B3F-315x_Angled +Button_Switch_THT:SW_SPST_Omron_B3F-40xx +Button_Switch_THT:SW_SPST_Omron_B3F-50xx Button_Switch_THT:SW_Tactile_SKHH_Angled Button_Switch_THT:SW_Tactile_SPST_Angled_PTS645Vx31-2LFS Button_Switch_THT:SW_Tactile_SPST_Angled_PTS645Vx39-2LFS @@ -332,6 +341,9 @@ Button_Switch_THT:SW_Tactile_SPST_Angled_PTS645Vx83-2LFS Button_Switch_THT:SW_Tactile_Straight_KSA0Axx1LFTR Button_Switch_THT:SW_Tactile_Straight_KSL0Axx1LFTR Button_Switch_THT:SW_TH_Tactile_Omron_B3F-10xx +Button_Switch_THT:SW_XKB_DM1-16UC-1 +Button_Switch_THT:SW_XKB_DM1-16UD-1 +Button_Switch_THT:SW_XKB_DM1-16UP-1 Buzzer_Beeper:Buzzer_12x9.5RM7.6 Buzzer_Beeper:Buzzer_15x7.5RM7.6 Buzzer_Beeper:Buzzer_CUI_CPT-9019S-SMT @@ -442,6 +454,8 @@ Capacitor_SMD:C_1206_3216Metric Capacitor_SMD:C_1206_3216Metric_Pad1.33x1.80mm_HandSolder Capacitor_SMD:C_1210_3225Metric Capacitor_SMD:C_1210_3225Metric_Pad1.33x2.70mm_HandSolder +Capacitor_SMD:C_1808_4520Metric +Capacitor_SMD:C_1808_4520Metric_Pad1.72x2.30mm_HandSolder Capacitor_SMD:C_1812_4532Metric Capacitor_SMD:C_1812_4532Metric_Pad1.57x3.40mm_HandSolder Capacitor_SMD:C_1825_4564Metric @@ -910,7 +924,12 @@ Capacitor_THT:C_Rect_L9.0mm_W9.8mm_P7.50mm_MKT Capacitor_THT:DX_5R5HxxxxU_D11.5mm_P10.00mm Capacitor_THT:DX_5R5VxxxxU_D11.5mm_P5.00mm Capacitor_THT:DX_5R5VxxxxU_D19.0mm_P5.00mm -Connector:Banana_Cliff_FCR7350x_S16N-PC_Horizontal +Connector:Banana_Cliff_FCR7350B_S16N-PC_Horizontal +Connector:Banana_Cliff_FCR7350G_S16N-PC_Horizontal +Connector:Banana_Cliff_FCR7350L_S16N-PC_Horizontal +Connector:Banana_Cliff_FCR7350N_S16N-PC_Horizontal +Connector:Banana_Cliff_FCR7350R_S16N-PC_Horizontal +Connector:Banana_Cliff_FCR7350Y_S16N-PC_Horizontal Connector:Banana_Jack_1Pin Connector:Banana_Jack_2Pin Connector:Banana_Jack_3Pin @@ -948,9 +967,13 @@ Connector_AMASS:AMASS_XT60-M_1x02_P7.20mm_Vertical Connector_AMASS:AMASS_XT60IPW-M_1x03_P7.20mm_Horizontal Connector_AMASS:AMASS_XT60PW-F_1x02_P7.20mm_Horizontal Connector_AMASS:AMASS_XT60PW-M_1x02_P7.20mm_Horizontal +Connector_AMASS:AMASS_XT90PW-M_1x02_P10.90mm_Horizontal Connector_Amphenol:Amphenol_M8S-03PMMR-SF8001 Connector_Audio:Jack_3.5mm_CUI_SJ-3523-SMT_Horizontal Connector_Audio:Jack_3.5mm_CUI_SJ-3524-SMT_Horizontal +Connector_Audio:Jack_3.5mm_CUI_SJ1-3513N_Horizontal +Connector_Audio:Jack_3.5mm_CUI_SJ1-3514N_Horizontal +Connector_Audio:Jack_3.5mm_CUI_SJ1-3515N_Horizontal Connector_Audio:Jack_3.5mm_CUI_SJ1-3523N_Horizontal Connector_Audio:Jack_3.5mm_CUI_SJ1-3524N_Horizontal Connector_Audio:Jack_3.5mm_CUI_SJ1-3525N_Horizontal @@ -1161,11 +1184,14 @@ Connector_BarrelJack:BarrelJack_CLIFF_FC681465S_SMT_Horizontal Connector_BarrelJack:BarrelJack_CUI_PJ-036AH-SMT_Horizontal Connector_BarrelJack:BarrelJack_CUI_PJ-063AH_Horizontal Connector_BarrelJack:BarrelJack_CUI_PJ-063AH_Horizontal_CircularHoles +Connector_BarrelJack:BarrelJack_CUI_PJ-079BH_Horizontal Connector_BarrelJack:BarrelJack_CUI_PJ-102AH_Horizontal Connector_BarrelJack:BarrelJack_GCT_DCJ200-10-A_Horizontal Connector_BarrelJack:BarrelJack_Horizontal Connector_BarrelJack:BarrelJack_Kycon_KLDX-0202-xC_Horizontal Connector_BarrelJack:BarrelJack_SwitchcraftConxall_RAPC10U_Horizontal +Connector_BarrelJack:BarrelJack_Wuerth_694102107102_1.0x3.9mm +Connector_BarrelJack:BarrelJack_Wuerth_694103107102_1.35x3.9mm Connector_BarrelJack:BarrelJack_Wuerth_694106106102_2.0x5.5mm Connector_BarrelJack:BarrelJack_Wuerth_694108106102_2.5x5.5mm Connector_BarrelJack:BarrelJack_Wuerth_6941xx301002 @@ -1178,6 +1204,11 @@ Connector_Card:microSD_HC_Molex_104031-0811 Connector_Card:microSD_HC_Molex_47219-2001 Connector_Card:microSD_HC_Wuerth_693072010801 Connector_Card:microSIM_JAE_SF53S006VCBR2000 +Connector_Card:nanoSIM_GCT_SIM8060-6-0-14-00 +Connector_Card:nanoSIM_GCT_SIM8060-6-1-14-00 +Connector_Card:nanoSIM_Hinged_CUI_NSIM-2-C +Connector_Card:SD-SIM_microSD-microSIM_Molex_104168-1620 +Connector_Card:SD_Card_Device_16mm_SlotDepth Connector_Card:SD_Hirose_DM1AA_SF_PEJ82 Connector_Card:SD_Kyocera_145638009211859+ Connector_Card:SD_Kyocera_145638009511859+ @@ -1192,6 +1223,8 @@ Connector_Coaxial:BNC_TEConnectivity_1478035_Horizontal Connector_Coaxial:BNC_TEConnectivity_1478204_Vertical Connector_Coaxial:BNC_Win_364A2x95_Horizontal Connector_Coaxial:CoaxialSwitch_Hirose_MS-156C3_Horizontal +Connector_Coaxial:LEMO-EPG.00.302.NLN +Connector_Coaxial:LEMO-EPL.00.250.NTN Connector_Coaxial:MMCX_Molex_73415-0961_Horizontal_0.8mm-PCB Connector_Coaxial:MMCX_Molex_73415-0961_Horizontal_1.0mm-PCB Connector_Coaxial:MMCX_Molex_73415-0961_Horizontal_1.6mm-PCB @@ -1207,10 +1240,11 @@ Connector_Coaxial:SMA_Amphenol_132291-12_Vertical Connector_Coaxial:SMA_Amphenol_132291_Vertical Connector_Coaxial:SMA_Amphenol_901-143_Horizontal Connector_Coaxial:SMA_Amphenol_901-144_Vertical +Connector_Coaxial:SMA_BAT_Wireless_BWSMA-KWE-Z001 Connector_Coaxial:SMA_Molex_73251-1153_EdgeMount_Horizontal Connector_Coaxial:SMA_Molex_73251-2120_EdgeMount_Horizontal Connector_Coaxial:SMA_Molex_73251-2200_Horizontal -Connector_Coaxial:SMA_Samtec_SMA-J-P-X-ST-EM1_EdgeMount +Connector_Coaxial:SMA_Samtec_SMA-J-P-H-ST-EM1_EdgeMount Connector_Coaxial:SMA_Wurth_60312002114503_Vertical Connector_Coaxial:SMA_Wurth_60312102114405_Vertical Connector_Coaxial:SMB_Jack_Vertical @@ -1290,126 +1324,110 @@ Connector_DIN:DIN41612_R_3x16_Female_Horizontal_THT Connector_DIN:DIN41612_R_3x16_Male_Vertical_THT Connector_DIN:DIN41612_R_3x32_Female_Horizontal_THT Connector_DIN:DIN41612_R_3x32_Male_Vertical_THT -Connector_Dsub:DSUB-15-HD_Female_Horizontal_P2.29x1.98mm_EdgePinOffset3.03mm_Housed_MountingHolesOffset4.94mm -Connector_Dsub:DSUB-15-HD_Female_Horizontal_P2.29x1.98mm_EdgePinOffset8.35mm_Housed_MountingHolesOffset10.89mm -Connector_Dsub:DSUB-15-HD_Female_Horizontal_P2.29x1.98mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-15-HD_Female_Horizontal_P2.29x2.54mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-15-HD_Female_Vertical_P2.29x1.98mm_MountingHoles -Connector_Dsub:DSUB-15-HD_Male_Horizontal_P2.29x1.98mm_EdgePinOffset3.03mm_Housed_MountingHolesOffset4.94mm -Connector_Dsub:DSUB-15-HD_Male_Horizontal_P2.29x1.98mm_EdgePinOffset8.35mm_Housed_MountingHolesOffset10.89mm -Connector_Dsub:DSUB-15-HD_Male_Horizontal_P2.29x1.98mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-15-HD_Male_Horizontal_P2.29x2.54mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-15-HD_Male_Vertical_P2.29x1.98mm_MountingHoles -Connector_Dsub:DSUB-15_Female_EdgeMount_P2.77mm -Connector_Dsub:DSUB-15_Female_Horizontal_P2.77x2.54mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-15_Female_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset15.98mm -Connector_Dsub:DSUB-15_Female_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset8.20mm -Connector_Dsub:DSUB-15_Female_Horizontal_P2.77x2.84mm_EdgePinOffset4.94mm_Housed_MountingHolesOffset7.48mm -Connector_Dsub:DSUB-15_Female_Horizontal_P2.77x2.84mm_EdgePinOffset7.70mm_Housed_MountingHolesOffset9.12mm -Connector_Dsub:DSUB-15_Female_Horizontal_P2.77x2.84mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-15_Female_Horizontal_P2.77x2.84mm_EdgePinOffset9.90mm_Housed_MountingHolesOffset11.32mm -Connector_Dsub:DSUB-15_Female_Vertical_P2.77x2.84mm -Connector_Dsub:DSUB-15_Female_Vertical_P2.77x2.84mm_MountingHoles -Connector_Dsub:DSUB-15_Male_EdgeMount_P2.77mm -Connector_Dsub:DSUB-15_Male_Horizontal_P2.77x2.54mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-15_Male_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset15.98mm -Connector_Dsub:DSUB-15_Male_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset8.20mm -Connector_Dsub:DSUB-15_Male_Horizontal_P2.77x2.84mm_EdgePinOffset4.94mm_Housed_MountingHolesOffset7.48mm -Connector_Dsub:DSUB-15_Male_Horizontal_P2.77x2.84mm_EdgePinOffset7.70mm_Housed_MountingHolesOffset9.12mm -Connector_Dsub:DSUB-15_Male_Horizontal_P2.77x2.84mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-15_Male_Horizontal_P2.77x2.84mm_EdgePinOffset9.90mm_Housed_MountingHolesOffset11.32mm -Connector_Dsub:DSUB-15_Male_Vertical_P2.77x2.84mm -Connector_Dsub:DSUB-15_Male_Vertical_P2.77x2.84mm_MountingHoles -Connector_Dsub:DSUB-25_Female_EdgeMount_P2.77mm -Connector_Dsub:DSUB-25_Female_Horizontal_P2.77x2.54mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-25_Female_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset15.98mm -Connector_Dsub:DSUB-25_Female_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset8.20mm -Connector_Dsub:DSUB-25_Female_Horizontal_P2.77x2.84mm_EdgePinOffset4.94mm_Housed_MountingHolesOffset7.48mm -Connector_Dsub:DSUB-25_Female_Horizontal_P2.77x2.84mm_EdgePinOffset7.70mm_Housed_MountingHolesOffset9.12mm -Connector_Dsub:DSUB-25_Female_Horizontal_P2.77x2.84mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-25_Female_Horizontal_P2.77x2.84mm_EdgePinOffset9.90mm_Housed_MountingHolesOffset11.32mm -Connector_Dsub:DSUB-25_Female_Vertical_P2.77x2.84mm -Connector_Dsub:DSUB-25_Female_Vertical_P2.77x2.84mm_MountingHoles -Connector_Dsub:DSUB-25_Male_EdgeMount_P2.77mm -Connector_Dsub:DSUB-25_Male_Horizontal_P2.77x2.54mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-25_Male_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset15.98mm -Connector_Dsub:DSUB-25_Male_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset8.20mm -Connector_Dsub:DSUB-25_Male_Horizontal_P2.77x2.84mm_EdgePinOffset4.94mm_Housed_MountingHolesOffset7.48mm -Connector_Dsub:DSUB-25_Male_Horizontal_P2.77x2.84mm_EdgePinOffset7.70mm_Housed_MountingHolesOffset9.12mm -Connector_Dsub:DSUB-25_Male_Horizontal_P2.77x2.84mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-25_Male_Horizontal_P2.77x2.84mm_EdgePinOffset9.90mm_Housed_MountingHolesOffset11.32mm -Connector_Dsub:DSUB-25_Male_Vertical_P2.77x2.84mm -Connector_Dsub:DSUB-25_Male_Vertical_P2.77x2.84mm_MountingHoles -Connector_Dsub:DSUB-26-HD_Female_Horizontal_P2.29x1.98mm_EdgePinOffset3.03mm_Housed_MountingHolesOffset4.94mm -Connector_Dsub:DSUB-26-HD_Female_Horizontal_P2.29x1.98mm_EdgePinOffset8.35mm_Housed_MountingHolesOffset10.89mm -Connector_Dsub:DSUB-26-HD_Female_Horizontal_P2.29x1.98mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-26-HD_Female_Horizontal_P2.29x2.54mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-26-HD_Female_Vertical_P2.29x1.98mm_MountingHoles -Connector_Dsub:DSUB-26-HD_Male_Horizontal_P2.29x1.98mm_EdgePinOffset3.03mm_Housed_MountingHolesOffset4.94mm -Connector_Dsub:DSUB-26-HD_Male_Horizontal_P2.29x1.98mm_EdgePinOffset8.35mm_Housed_MountingHolesOffset10.89mm -Connector_Dsub:DSUB-26-HD_Male_Horizontal_P2.29x1.98mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-26-HD_Male_Horizontal_P2.29x2.54mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-26-HD_Male_Vertical_P2.29x1.98mm_MountingHoles -Connector_Dsub:DSUB-37_Female_EdgeMount_P2.77mm -Connector_Dsub:DSUB-37_Female_Horizontal_P2.77x2.54mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-37_Female_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset15.98mm -Connector_Dsub:DSUB-37_Female_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset8.20mm -Connector_Dsub:DSUB-37_Female_Horizontal_P2.77x2.84mm_EdgePinOffset4.94mm_Housed_MountingHolesOffset7.48mm -Connector_Dsub:DSUB-37_Female_Horizontal_P2.77x2.84mm_EdgePinOffset7.70mm_Housed_MountingHolesOffset9.12mm -Connector_Dsub:DSUB-37_Female_Horizontal_P2.77x2.84mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-37_Female_Horizontal_P2.77x2.84mm_EdgePinOffset9.90mm_Housed_MountingHolesOffset11.32mm -Connector_Dsub:DSUB-37_Female_Vertical_P2.77x2.84mm -Connector_Dsub:DSUB-37_Female_Vertical_P2.77x2.84mm_MountingHoles -Connector_Dsub:DSUB-37_Male_EdgeMount_P2.77mm -Connector_Dsub:DSUB-37_Male_Horizontal_P2.77x2.54mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-37_Male_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset15.98mm -Connector_Dsub:DSUB-37_Male_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset8.20mm -Connector_Dsub:DSUB-37_Male_Horizontal_P2.77x2.84mm_EdgePinOffset4.94mm_Housed_MountingHolesOffset7.48mm -Connector_Dsub:DSUB-37_Male_Horizontal_P2.77x2.84mm_EdgePinOffset7.70mm_Housed_MountingHolesOffset9.12mm -Connector_Dsub:DSUB-37_Male_Horizontal_P2.77x2.84mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-37_Male_Horizontal_P2.77x2.84mm_EdgePinOffset9.90mm_Housed_MountingHolesOffset11.32mm -Connector_Dsub:DSUB-37_Male_Vertical_P2.77x2.84mm -Connector_Dsub:DSUB-37_Male_Vertical_P2.77x2.84mm_MountingHoles -Connector_Dsub:DSUB-44-HD_Female_Horizontal_P2.29x1.98mm_EdgePinOffset3.03mm_Housed_MountingHolesOffset4.94mm -Connector_Dsub:DSUB-44-HD_Female_Horizontal_P2.29x1.98mm_EdgePinOffset8.35mm_Housed_MountingHolesOffset10.89mm -Connector_Dsub:DSUB-44-HD_Female_Horizontal_P2.29x1.98mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-44-HD_Female_Horizontal_P2.29x2.54mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-44-HD_Female_Vertical_P2.29x1.98mm_MountingHoles -Connector_Dsub:DSUB-44-HD_Male_Horizontal_P2.29x1.98mm_EdgePinOffset3.03mm_Housed_MountingHolesOffset4.94mm -Connector_Dsub:DSUB-44-HD_Male_Horizontal_P2.29x1.98mm_EdgePinOffset8.35mm_Housed_MountingHolesOffset10.89mm -Connector_Dsub:DSUB-44-HD_Male_Horizontal_P2.29x1.98mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-44-HD_Male_Horizontal_P2.29x2.54mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-44-HD_Male_Vertical_P2.29x1.98mm_MountingHoles -Connector_Dsub:DSUB-62-HD_Female_Horizontal_P2.41x1.98mm_EdgePinOffset3.03mm_Housed_MountingHolesOffset4.94mm -Connector_Dsub:DSUB-62-HD_Female_Horizontal_P2.41x1.98mm_EdgePinOffset8.35mm_Housed_MountingHolesOffset10.89mm -Connector_Dsub:DSUB-62-HD_Female_Horizontal_P2.41x1.98mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-62-HD_Female_Horizontal_P2.41x2.54mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-62-HD_Female_Vertical_P2.41x1.98mm_MountingHoles -Connector_Dsub:DSUB-62-HD_Male_Horizontal_P2.41x1.98mm_EdgePinOffset3.03mm_Housed_MountingHolesOffset4.94mm -Connector_Dsub:DSUB-62-HD_Male_Horizontal_P2.41x1.98mm_EdgePinOffset8.35mm_Housed_MountingHolesOffset10.89mm -Connector_Dsub:DSUB-62-HD_Male_Horizontal_P2.41x1.98mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-62-HD_Male_Horizontal_P2.41x2.54mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-62-HD_Male_Vertical_P2.41x1.98mm_MountingHoles -Connector_Dsub:DSUB-9_Female_EdgeMount_P2.77mm -Connector_Dsub:DSUB-9_Female_Horizontal_P2.77x2.54mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-9_Female_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset15.98mm -Connector_Dsub:DSUB-9_Female_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset8.20mm -Connector_Dsub:DSUB-9_Female_Horizontal_P2.77x2.84mm_EdgePinOffset4.94mm_Housed_MountingHolesOffset7.48mm -Connector_Dsub:DSUB-9_Female_Horizontal_P2.77x2.84mm_EdgePinOffset7.70mm_Housed_MountingHolesOffset9.12mm -Connector_Dsub:DSUB-9_Female_Horizontal_P2.77x2.84mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-9_Female_Horizontal_P2.77x2.84mm_EdgePinOffset9.90mm_Housed_MountingHolesOffset11.32mm -Connector_Dsub:DSUB-9_Female_Vertical_P2.77x2.84mm -Connector_Dsub:DSUB-9_Female_Vertical_P2.77x2.84mm_MountingHoles -Connector_Dsub:DSUB-9_Male_EdgeMount_P2.77mm -Connector_Dsub:DSUB-9_Male_Horizontal_P2.77x2.54mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-9_Male_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset15.98mm -Connector_Dsub:DSUB-9_Male_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset8.20mm -Connector_Dsub:DSUB-9_Male_Horizontal_P2.77x2.84mm_EdgePinOffset4.94mm_Housed_MountingHolesOffset7.48mm -Connector_Dsub:DSUB-9_Male_Horizontal_P2.77x2.84mm_EdgePinOffset7.70mm_Housed_MountingHolesOffset9.12mm -Connector_Dsub:DSUB-9_Male_Horizontal_P2.77x2.84mm_EdgePinOffset9.40mm -Connector_Dsub:DSUB-9_Male_Horizontal_P2.77x2.84mm_EdgePinOffset9.90mm_Housed_MountingHolesOffset11.32mm -Connector_Dsub:DSUB-9_Male_Vertical_P2.77x2.84mm -Connector_Dsub:DSUB-9_Male_Vertical_P2.77x2.84mm_MountingHoles +Connector_Dsub:DSUB-15-HD_Pins_Horizontal_P2.29x1.90mm_EdgePinOffset3.03mm_Housed_MountingHolesOffset4.94mm +Connector_Dsub:DSUB-15-HD_Pins_Horizontal_P2.29x2.54mm_EdgePinOffset8.35mm_Housed_MountingHolesOffset10.89mm +Connector_Dsub:DSUB-15-HD_Pins_Vertical_P2.29x1.98mm_MountingHoles +Connector_Dsub:DSUB-15-HD_Socket_Horizontal_P2.29x1.90mm_EdgePinOffset3.03mm_Housed_MountingHolesOffset4.94mm +Connector_Dsub:DSUB-15-HD_Socket_Horizontal_P2.29x2.54mm_EdgePinOffset8.35mm_Housed_MountingHolesOffset10.89mm +Connector_Dsub:DSUB-15-HD_Socket_Vertical_P2.29x1.98mm_MountingHoles +Connector_Dsub:DSUB-15_Pins_EdgeMount_P2.77mm +Connector_Dsub:DSUB-15_Pins_Horizontal_P2.77x2.54mm_EdgePinOffset9.40mm +Connector_Dsub:DSUB-15_Pins_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset15.98mm +Connector_Dsub:DSUB-15_Pins_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset8.20mm +Connector_Dsub:DSUB-15_Pins_Horizontal_P2.77x2.84mm_EdgePinOffset4.94mm_Housed_MountingHolesOffset4.94mm +Connector_Dsub:DSUB-15_Pins_Horizontal_P2.77x2.84mm_EdgePinOffset7.70mm_Housed_MountingHolesOffset9.12mm +Connector_Dsub:DSUB-15_Pins_Horizontal_P2.77x2.84mm_EdgePinOffset9.40mm +Connector_Dsub:DSUB-15_Pins_Horizontal_P2.77x2.84mm_EdgePinOffset9.90mm_Housed_MountingHolesOffset11.32mm +Connector_Dsub:DSUB-15_Pins_Vertical_P2.77x2.84mm +Connector_Dsub:DSUB-15_Pins_Vertical_P2.77x2.84mm_MountingHoles +Connector_Dsub:DSUB-15_Socket_EdgeMount_P2.77mm +Connector_Dsub:DSUB-15_Socket_Horizontal_P2.77x2.54mm_EdgePinOffset9.40mm +Connector_Dsub:DSUB-15_Socket_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset15.98mm +Connector_Dsub:DSUB-15_Socket_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset8.20mm +Connector_Dsub:DSUB-15_Socket_Horizontal_P2.77x2.84mm_EdgePinOffset4.94mm_Housed_MountingHolesOffset4.94mm +Connector_Dsub:DSUB-15_Socket_Horizontal_P2.77x2.84mm_EdgePinOffset7.70mm_Housed_MountingHolesOffset9.12mm +Connector_Dsub:DSUB-15_Socket_Horizontal_P2.77x2.84mm_EdgePinOffset9.40mm +Connector_Dsub:DSUB-15_Socket_Horizontal_P2.77x2.84mm_EdgePinOffset9.90mm_Housed_MountingHolesOffset11.32mm +Connector_Dsub:DSUB-15_Socket_Vertical_P2.77x2.84mm +Connector_Dsub:DSUB-15_Socket_Vertical_P2.77x2.84mm_MountingHoles +Connector_Dsub:DSUB-25_Pins_EdgeMount_P2.77mm +Connector_Dsub:DSUB-25_Pins_Horizontal_P2.77x2.54mm_EdgePinOffset9.40mm +Connector_Dsub:DSUB-25_Pins_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset15.98mm +Connector_Dsub:DSUB-25_Pins_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset8.20mm +Connector_Dsub:DSUB-25_Pins_Horizontal_P2.77x2.84mm_EdgePinOffset4.94mm_Housed_MountingHolesOffset4.94mm +Connector_Dsub:DSUB-25_Pins_Horizontal_P2.77x2.84mm_EdgePinOffset7.70mm_Housed_MountingHolesOffset9.12mm +Connector_Dsub:DSUB-25_Pins_Horizontal_P2.77x2.84mm_EdgePinOffset9.40mm +Connector_Dsub:DSUB-25_Pins_Horizontal_P2.77x2.84mm_EdgePinOffset9.90mm_Housed_MountingHolesOffset11.32mm +Connector_Dsub:DSUB-25_Pins_Vertical_P2.77x2.84mm +Connector_Dsub:DSUB-25_Pins_Vertical_P2.77x2.84mm_MountingHoles +Connector_Dsub:DSUB-25_Socket_EdgeMount_P2.77mm +Connector_Dsub:DSUB-25_Socket_Horizontal_P2.77x2.54mm_EdgePinOffset9.40mm +Connector_Dsub:DSUB-25_Socket_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset15.98mm +Connector_Dsub:DSUB-25_Socket_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset8.20mm +Connector_Dsub:DSUB-25_Socket_Horizontal_P2.77x2.84mm_EdgePinOffset4.94mm_Housed_MountingHolesOffset4.94mm +Connector_Dsub:DSUB-25_Socket_Horizontal_P2.77x2.84mm_EdgePinOffset7.70mm_Housed_MountingHolesOffset9.12mm +Connector_Dsub:DSUB-25_Socket_Horizontal_P2.77x2.84mm_EdgePinOffset9.40mm +Connector_Dsub:DSUB-25_Socket_Horizontal_P2.77x2.84mm_EdgePinOffset9.90mm_Housed_MountingHolesOffset11.32mm +Connector_Dsub:DSUB-25_Socket_Vertical_P2.77x2.84mm +Connector_Dsub:DSUB-25_Socket_Vertical_P2.77x2.84mm_MountingHoles +Connector_Dsub:DSUB-26-HD_Pins_Horizontal_P2.29x1.90mm_EdgePinOffset3.03mm_Housed_MountingHolesOffset4.94mm +Connector_Dsub:DSUB-26-HD_Pins_Horizontal_P2.29x2.54mm_EdgePinOffset8.35mm_Housed_MountingHolesOffset10.89mm +Connector_Dsub:DSUB-26-HD_Pins_Vertical_P2.29x1.98mm_MountingHoles +Connector_Dsub:DSUB-26-HD_Socket_Horizontal_P2.29x1.90mm_EdgePinOffset3.03mm_Housed_MountingHolesOffset4.94mm +Connector_Dsub:DSUB-26-HD_Socket_Horizontal_P2.29x2.54mm_EdgePinOffset8.35mm_Housed_MountingHolesOffset10.89mm +Connector_Dsub:DSUB-26-HD_Socket_Vertical_P2.29x1.98mm_MountingHoles +Connector_Dsub:DSUB-37_Pins_EdgeMount_P2.77mm +Connector_Dsub:DSUB-37_Pins_Horizontal_P2.77x2.54mm_EdgePinOffset9.40mm +Connector_Dsub:DSUB-37_Pins_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset15.98mm +Connector_Dsub:DSUB-37_Pins_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset8.20mm +Connector_Dsub:DSUB-37_Pins_Horizontal_P2.77x2.84mm_EdgePinOffset4.94mm_Housed_MountingHolesOffset4.94mm +Connector_Dsub:DSUB-37_Pins_Horizontal_P2.77x2.84mm_EdgePinOffset7.70mm_Housed_MountingHolesOffset9.12mm +Connector_Dsub:DSUB-37_Pins_Horizontal_P2.77x2.84mm_EdgePinOffset9.40mm +Connector_Dsub:DSUB-37_Pins_Horizontal_P2.77x2.84mm_EdgePinOffset9.90mm_Housed_MountingHolesOffset11.32mm +Connector_Dsub:DSUB-37_Pins_Vertical_P2.77x2.84mm +Connector_Dsub:DSUB-37_Pins_Vertical_P2.77x2.84mm_MountingHoles +Connector_Dsub:DSUB-37_Socket_EdgeMount_P2.77mm +Connector_Dsub:DSUB-37_Socket_Horizontal_P2.77x2.54mm_EdgePinOffset9.40mm +Connector_Dsub:DSUB-37_Socket_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset15.98mm +Connector_Dsub:DSUB-37_Socket_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset8.20mm +Connector_Dsub:DSUB-37_Socket_Horizontal_P2.77x2.84mm_EdgePinOffset4.94mm_Housed_MountingHolesOffset4.94mm +Connector_Dsub:DSUB-37_Socket_Horizontal_P2.77x2.84mm_EdgePinOffset7.70mm_Housed_MountingHolesOffset9.12mm +Connector_Dsub:DSUB-37_Socket_Horizontal_P2.77x2.84mm_EdgePinOffset9.40mm +Connector_Dsub:DSUB-37_Socket_Horizontal_P2.77x2.84mm_EdgePinOffset9.90mm_Housed_MountingHolesOffset11.32mm +Connector_Dsub:DSUB-37_Socket_Vertical_P2.77x2.84mm +Connector_Dsub:DSUB-37_Socket_Vertical_P2.77x2.84mm_MountingHoles +Connector_Dsub:DSUB-44-HD_Pins_Horizontal_P2.29x1.90mm_EdgePinOffset3.03mm_Housed_MountingHolesOffset4.94mm +Connector_Dsub:DSUB-44-HD_Pins_Horizontal_P2.29x2.54mm_EdgePinOffset8.35mm_Housed_MountingHolesOffset10.89mm +Connector_Dsub:DSUB-44-HD_Pins_Vertical_P2.29x1.98mm_MountingHoles +Connector_Dsub:DSUB-44-HD_Socket_Horizontal_P2.29x1.90mm_EdgePinOffset3.03mm_Housed_MountingHolesOffset4.94mm +Connector_Dsub:DSUB-44-HD_Socket_Horizontal_P2.29x2.54mm_EdgePinOffset8.35mm_Housed_MountingHolesOffset10.89mm +Connector_Dsub:DSUB-44-HD_Socket_Vertical_P2.29x1.98mm_MountingHoles +Connector_Dsub:DSUB-62-HD_Pins_Horizontal_P2.41x1.90mm_EdgePinOffset3.03mm_Housed_MountingHolesOffset4.94mm +Connector_Dsub:DSUB-62-HD_Pins_Horizontal_P2.41x2.54mm_EdgePinOffset8.35mm_Housed_MountingHolesOffset10.89mm +Connector_Dsub:DSUB-62-HD_Pins_Vertical_P2.41x1.98mm_MountingHoles +Connector_Dsub:DSUB-62-HD_Socket_Horizontal_P2.41x1.90mm_EdgePinOffset3.03mm_Housed_MountingHolesOffset4.94mm +Connector_Dsub:DSUB-62-HD_Socket_Horizontal_P2.41x2.54mm_EdgePinOffset8.35mm_Housed_MountingHolesOffset10.89mm +Connector_Dsub:DSUB-62-HD_Socket_Vertical_P2.41x1.98mm_MountingHoles +Connector_Dsub:DSUB-9_Pins_EdgeMount_P2.77mm +Connector_Dsub:DSUB-9_Pins_Horizontal_P2.77x2.54mm_EdgePinOffset9.40mm +Connector_Dsub:DSUB-9_Pins_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset15.98mm +Connector_Dsub:DSUB-9_Pins_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset8.20mm +Connector_Dsub:DSUB-9_Pins_Horizontal_P2.77x2.84mm_EdgePinOffset4.94mm_Housed_MountingHolesOffset4.94mm +Connector_Dsub:DSUB-9_Pins_Horizontal_P2.77x2.84mm_EdgePinOffset7.70mm_Housed_MountingHolesOffset9.12mm +Connector_Dsub:DSUB-9_Pins_Horizontal_P2.77x2.84mm_EdgePinOffset9.40mm +Connector_Dsub:DSUB-9_Pins_Horizontal_P2.77x2.84mm_EdgePinOffset9.90mm_Housed_MountingHolesOffset11.32mm +Connector_Dsub:DSUB-9_Pins_Vertical_P2.77x2.84mm +Connector_Dsub:DSUB-9_Pins_Vertical_P2.77x2.84mm_MountingHoles +Connector_Dsub:DSUB-9_Socket_EdgeMount_P2.77mm +Connector_Dsub:DSUB-9_Socket_Horizontal_P2.77x2.54mm_EdgePinOffset9.40mm +Connector_Dsub:DSUB-9_Socket_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset15.98mm +Connector_Dsub:DSUB-9_Socket_Horizontal_P2.77x2.84mm_EdgePinOffset14.56mm_Housed_MountingHolesOffset8.20mm +Connector_Dsub:DSUB-9_Socket_Horizontal_P2.77x2.84mm_EdgePinOffset4.94mm_Housed_MountingHolesOffset4.94mm +Connector_Dsub:DSUB-9_Socket_Horizontal_P2.77x2.84mm_EdgePinOffset7.70mm_Housed_MountingHolesOffset9.12mm +Connector_Dsub:DSUB-9_Socket_Horizontal_P2.77x2.84mm_EdgePinOffset9.40mm +Connector_Dsub:DSUB-9_Socket_Horizontal_P2.77x2.84mm_EdgePinOffset9.90mm_Housed_MountingHolesOffset11.32mm +Connector_Dsub:DSUB-9_Socket_Vertical_P2.77x2.84mm +Connector_Dsub:DSUB-9_Socket_Vertical_P2.77x2.84mm_MountingHoles Connector_FFC-FPC:Hirose_FH12-10S-0.5SH_1x10-1MP_P0.50mm_Horizontal Connector_FFC-FPC:Hirose_FH12-11S-0.5SH_1x11-1MP_P0.50mm_Horizontal Connector_FFC-FPC:Hirose_FH12-12S-0.5SH_1x12-1MP_P0.50mm_Horizontal @@ -1438,6 +1456,27 @@ Connector_FFC-FPC:Hirose_FH12-50S-0.5SH_1x50-1MP_P0.50mm_Horizontal Connector_FFC-FPC:Hirose_FH12-53S-0.5SH_1x53-1MP_P0.50mm_Horizontal Connector_FFC-FPC:Hirose_FH12-6S-0.5SH_1x06-1MP_P0.50mm_Horizontal Connector_FFC-FPC:Hirose_FH12-8S-0.5SH_1x08-1MP_P0.50mm_Horizontal +Connector_FFC-FPC:Hirose_FH26-13S-0.3SHW_2Rows-13Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Hirose_FH26-15S-0.3SHW_2Rows-15Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Hirose_FH26-17S-0.3SHW_2Rows-17Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Hirose_FH26-19S-0.3SHW_2Rows-19Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Hirose_FH26-21S-0.3SHW_2Rows-21Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Hirose_FH26-23S-0.3SHW_2Rows-23Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Hirose_FH26-25S-0.3SHW_2Rows-25Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Hirose_FH26-27S-0.3SHW_2Rows-27Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Hirose_FH26-29S-0.3SHW_2Rows-29Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Hirose_FH26-31S-0.3SHW_2Rows-31Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Hirose_FH26-33S-0.3SHW_2Rows-33Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Hirose_FH26-35S-0.3SHW_2Rows-35Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Hirose_FH26-37S-0.3SHW_2Rows-37Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Hirose_FH26-39S-0.3SHW_2Rows-39Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Hirose_FH26-41S-0.3SHW_2Rows-41Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Hirose_FH26-45S-0.3SHW_2Rows-45Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Hirose_FH26-51S-0.3SHW_2Rows-51Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Hirose_FH26-55S-0.3SHW_2Rows-55Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Hirose_FH26-57S-0.3SHW_2Rows-57Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Hirose_FH26-61S-0.3SHW_2Rows-61Pins-1MP_P0.60mm_Horizontal +Connector_FFC-FPC:Hirose_FH26-71S-0.3SHW_2Rows-71Pins-1MP_P0.60mm_Horizontal Connector_FFC-FPC:Hirose_FH41-30S-0.5SH_1x30_1MP_1SH_P0.5mm_Horizontal Connector_FFC-FPC:JAE_FF0825SA1_2Rows-25Pins_P0.40mm_Horizontal Connector_FFC-FPC:JAE_FF0829SA1_2Rows-29Pins_P0.40mm_Horizontal @@ -1445,6 +1484,32 @@ Connector_FFC-FPC:JAE_FF0841SA1_2Rows-41Pins_P0.40mm_Horizontal Connector_FFC-FPC:JAE_FF0851SA1_2Rows-51Pins_P0.40mm_Horizontal Connector_FFC-FPC:JAE_FF0871SA1_2Rows-71Pins_P0.40mm_Horizontal Connector_FFC-FPC:JAE_FF0881SA1_2Rows-81Pins_P0.40mm_Horizontal +Connector_FFC-FPC:JUSHUO_AFA07-S04FCA-00_1x4-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:JUSHUO_AFA07-S05FCA-00_1x5-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:JUSHUO_AFA07-S06FCA-00_1x6-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:JUSHUO_AFA07-S07FCA-00_1x7-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:JUSHUO_AFA07-S08FCA-00_1x8-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:JUSHUO_AFA07-S09FCA-00_1x9-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:JUSHUO_AFA07-S10FCA-00_1x10-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:JUSHUO_AFA07-S11FCA-00_1x11-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:JUSHUO_AFA07-S12FCA-00_1x12-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:JUSHUO_AFA07-S13FCA-00_1x13-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:JUSHUO_AFA07-S14FCA-00_1x14-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:JUSHUO_AFA07-S15FCA-00_1x15-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:JUSHUO_AFA07-S16FCA-00_1x16-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:JUSHUO_AFA07-S17FCA-00_1x17-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:JUSHUO_AFA07-S18FCA-00_1x18-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:JUSHUO_AFA07-S19FCA-00_1x19-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:JUSHUO_AFA07-S20FCA-00_1x20-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:JUSHUO_AFA07-S21FCA-00_1x21-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:JUSHUO_AFA07-S22FCA-00_1x22-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:JUSHUO_AFA07-S23FCA-00_1x23-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:JUSHUO_AFA07-S24FCA-00_1x24-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:JUSHUO_AFA07-S25FCA-00_1x25-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:JUSHUO_AFA07-S26FCA-00_1x26-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:JUSHUO_AFA07-S27FCA-00_1x27-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:JUSHUO_AFA07-S28FCA-00_1x28-1MP_P1.0mm_Horizontal +Connector_FFC-FPC:JUSHUO_AFA07-S29FCA-00_1x29-1MP_P1.0mm_Horizontal Connector_FFC-FPC:Jushuo_AFC07-S06FCA-00_1x6-1MP_P0.50_Horizontal Connector_FFC-FPC:Jushuo_AFC07-S24FCA-00_1x24-1MP_P0.50_Horizontal Connector_FFC-FPC:Molex_200528-0040_1x04-1MP_P1.00mm_Horizontal @@ -1721,13 +1786,8 @@ Connector_Harwin:Harwin_M20-89017xx_1x17_P2.54mm_Horizontal Connector_Harwin:Harwin_M20-89018xx_1x18_P2.54mm_Horizontal Connector_Harwin:Harwin_M20-89019xx_1x19_P2.54mm_Horizontal Connector_Harwin:Harwin_M20-89020xx_1x20_P2.54mm_Horizontal -Connector_HDMI:HDMI_A_Amphenol_10029449-x01xLF_Horizontal -Connector_HDMI:HDMI_A_Contact_Technology_HDMI-19APL2_Horizontal -Connector_HDMI:HDMI_A_Kycon_KDMIX-SL1-NS-WS-B15_VerticalRightAngle -Connector_HDMI:HDMI_A_Molex_208658-1001_Horizontal -Connector_HDMI:HDMI_Micro-D_Molex_46765-0x01 -Connector_HDMI:HDMI_Micro-D_Molex_46765-1x01 -Connector_HDMI:HDMI_Micro-D_Molex_46765-2x0x +Connector_Hirose:Hirose_BM23FR0.6-16DP-0.35V_2x08_1MP_Vertical +Connector_Hirose:Hirose_BM23FR0.6-16DS-0.35V_2x08_P0.35_1MP_Vertical Connector_Hirose:Hirose_BM24_BM24-40DP-2-0.35V_2x20_P0.35mm_PowerPin2_Vertical Connector_Hirose:Hirose_BM24_BM24-40DS-2-0.35V_2x20_P0.35mm_PowerPin2_Vertical Connector_Hirose:Hirose_DF11-10DP-2DSA_2x05_P2.00mm_Vertical @@ -1842,6 +1902,18 @@ Connector_Hirose:Hirose_DF63R-2P-3.96DSA_1x02_P3.96mm_Vertical Connector_Hirose:Hirose_DF63R-3P-3.96DSA_1x03_P3.96mm_Vertical Connector_Hirose:Hirose_DF63R-4P-3.96DSA_1x04_P3.96mm_Vertical Connector_Hirose:Hirose_DF63R-5P-3.96DSA_1x05_P3.96mm_Vertical +Connector_Hirose_FX8:Hirose_FX8-100P-SV_2x50_P0.6mm +Connector_Hirose_FX8:Hirose_FX8-100S-SV_2x50_P0.6mm +Connector_Hirose_FX8:Hirose_FX8-120P-SV_2x60_P0.6mm +Connector_Hirose_FX8:Hirose_FX8-120S-SV_2x60_P0.6mm +Connector_Hirose_FX8:Hirose_FX8-140P-SV_2x70_P0.6mm +Connector_Hirose_FX8:Hirose_FX8-140S-SV_2x70_P0.6mm +Connector_Hirose_FX8:Hirose_FX8-60P-SV_2x30_P0.6mm +Connector_Hirose_FX8:Hirose_FX8-60S-SV_2x30_P0.6mm +Connector_Hirose_FX8:Hirose_FX8-80P-SV_2x40_P0.6mm +Connector_Hirose_FX8:Hirose_FX8-80S-SV_2x40_P0.6mm +Connector_Hirose_FX8:Hirose_FX8-90P-SV_2x45_P0.6mm +Connector_Hirose_FX8:Hirose_FX8-90S-SV_2x45_P0.6mm Connector_IDC:IDC-Header_2x03_P2.54mm_Horizontal Connector_IDC:IDC-Header_2x03_P2.54mm_Vertical Connector_IDC:IDC-Header_2x03_P2.54mm_Vertical_SMD @@ -1901,6 +1973,9 @@ Connector_IDC:IDC-Header_2x08_P2.54mm_Latch_Horizontal Connector_IDC:IDC-Header_2x08_P2.54mm_Latch_Vertical Connector_IDC:IDC-Header_2x08_P2.54mm_Vertical Connector_IDC:IDC-Header_2x08_P2.54mm_Vertical_SMD +Connector_IDC:IDC-Header_2x09_P2.54mm_Horizontal +Connector_IDC:IDC-Header_2x09_P2.54mm_Vertical +Connector_IDC:IDC-Header_2x09_P2.54mm_Vertical_SMD Connector_IDC:IDC-Header_2x10-1MP_P2.54mm_Latch12.0mm_Vertical Connector_IDC:IDC-Header_2x10-1MP_P2.54mm_Latch6.5mm_Vertical Connector_IDC:IDC-Header_2x10-1MP_P2.54mm_Latch9.5mm_Vertical @@ -1914,6 +1989,9 @@ Connector_IDC:IDC-Header_2x10_P2.54mm_Latch_Horizontal Connector_IDC:IDC-Header_2x10_P2.54mm_Latch_Vertical Connector_IDC:IDC-Header_2x10_P2.54mm_Vertical Connector_IDC:IDC-Header_2x10_P2.54mm_Vertical_SMD +Connector_IDC:IDC-Header_2x11_P2.54mm_Horizontal +Connector_IDC:IDC-Header_2x11_P2.54mm_Vertical +Connector_IDC:IDC-Header_2x11_P2.54mm_Vertical_SMD Connector_IDC:IDC-Header_2x12-1MP_P2.54mm_Latch12.0mm_Vertical Connector_IDC:IDC-Header_2x12-1MP_P2.54mm_Latch6.5mm_Vertical Connector_IDC:IDC-Header_2x12-1MP_P2.54mm_Latch9.5mm_Vertical @@ -1977,6 +2055,9 @@ Connector_IDC:IDC-Header_2x20_P2.54mm_Latch_Horizontal Connector_IDC:IDC-Header_2x20_P2.54mm_Latch_Vertical Connector_IDC:IDC-Header_2x20_P2.54mm_Vertical Connector_IDC:IDC-Header_2x20_P2.54mm_Vertical_SMD +Connector_IDC:IDC-Header_2x22_P2.54mm_Horizontal +Connector_IDC:IDC-Header_2x22_P2.54mm_Vertical +Connector_IDC:IDC-Header_2x22_P2.54mm_Vertical_SMD Connector_IDC:IDC-Header_2x25-1MP_P2.54mm_Latch12.0mm_Vertical Connector_IDC:IDC-Header_2x25-1MP_P2.54mm_Latch6.5mm_Vertical Connector_IDC:IDC-Header_2x25-1MP_P2.54mm_Latch9.5mm_Vertical @@ -2059,6 +2140,26 @@ Connector_JAE:JAE_LY20-8P-DLT1_2x04_P2.00mm_Horizontal Connector_JAE:JAE_LY20-8P-DT1_2x04_P2.00mm_Vertical Connector_JAE:JAE_MM70-314-310B1 Connector_JAE:JAE_SIM_Card_SF72S006 +Connector_JAE_WP7B:JAE_WP7B-P034VA1-R8000_2x17-1MP_P0.4mm +Connector_JAE_WP7B:JAE_WP7B-P034VA1-R8000_Longpads_2x17-1MP_P0.4mm +Connector_JAE_WP7B:JAE_WP7B-P040VA1-R8000_2x20-1MP_P0.4mm +Connector_JAE_WP7B:JAE_WP7B-P040VA1-R8000_Longpads_2x20-1MP_P0.4mm +Connector_JAE_WP7B:JAE_WP7B-P050VA1-R8000_2x25-1MP_P0.4mm +Connector_JAE_WP7B:JAE_WP7B-P050VA1-R8000_Longpads_2x25-1MP_P0.4mm +Connector_JAE_WP7B:JAE_WP7B-P060VA1-R8000_2x30-1MP_P0.4mm +Connector_JAE_WP7B:JAE_WP7B-P060VA1-R8000_Longpads_2x30-1MP_P0.4mm +Connector_JAE_WP7B:JAE_WP7B-P070VA1-R8000_2x35-1MP_P0.4mm +Connector_JAE_WP7B:JAE_WP7B-P070VA1-R8000_Longpads_2x35-1MP_P0.4mm +Connector_JAE_WP7B:JAE_WP7B-S034VA1-R8000_2x17-1MP_P0.4mm +Connector_JAE_WP7B:JAE_WP7B-S034VA1-R8000_Longpads_2x17-1MP_P0.4mm +Connector_JAE_WP7B:JAE_WP7B-S040VA1-R8000_2x20-1MP_P0.4mm +Connector_JAE_WP7B:JAE_WP7B-S040VA1-R8000_Longpads_2x20-1MP_P0.4mm +Connector_JAE_WP7B:JAE_WP7B-S050VA1-R8000_2x25-1MP_P0.4mm +Connector_JAE_WP7B:JAE_WP7B-S050VA1-R8000_Longpads_2x25-1MP_P0.4mm +Connector_JAE_WP7B:JAE_WP7B-S060VA1-R8000_2x30-1MP_P0.4mm +Connector_JAE_WP7B:JAE_WP7B-S060VA1-R8000_Longpads_2x30-1MP_P0.4mm +Connector_JAE_WP7B:JAE_WP7B-S070VA1-R8000_2x35-1MP_P0.4mm +Connector_JAE_WP7B:JAE_WP7B-S070VA1-R8000_Longpads_2x35-1MP_P0.4mm Connector_JST:JST_ACH_BM01B-ACHSS-A-GAN-ETF_1x01-1MP_P1.20mm_Vertical Connector_JST:JST_ACH_BM02B-ACHSS-GAN-ETF_1x02-1MP_P1.20mm_Vertical Connector_JST:JST_ACH_BM03B-ACHSS-GAN-ETF_1x03-1MP_P1.20mm_Vertical @@ -2481,12 +2582,15 @@ Connector_JST:JST_XH_S2B-XH-A-1_1x02_P2.50mm_Horizontal Connector_JST:JST_XH_S2B-XH-A_1x02_P2.50mm_Horizontal Connector_JST:JST_XH_S3B-XH-A-1_1x03_P2.50mm_Horizontal Connector_JST:JST_XH_S3B-XH-A_1x03_P2.50mm_Horizontal +Connector_JST:JST_XH_S3B-XH-SM4-TB_1x03-1MP_P2.50mm_Horizontal Connector_JST:JST_XH_S4B-XH-A-1_1x04_P2.50mm_Horizontal Connector_JST:JST_XH_S4B-XH-A_1x04_P2.50mm_Horizontal +Connector_JST:JST_XH_S4B-XH-SM4-TB_1x04-1MP_P2.50mm_Horizontal Connector_JST:JST_XH_S5B-XH-A-1_1x05_P2.50mm_Horizontal Connector_JST:JST_XH_S5B-XH-A_1x05_P2.50mm_Horizontal Connector_JST:JST_XH_S6B-XH-A-1_1x06_P2.50mm_Horizontal Connector_JST:JST_XH_S6B-XH-A_1x06_P2.50mm_Horizontal +Connector_JST:JST_XH_S6B-XH-SM4-TB_1x06-1MP_P2.50mm_Horizontal Connector_JST:JST_XH_S7B-XH-A-1_1x07_P2.50mm_Horizontal Connector_JST:JST_XH_S7B-XH-A_1x07_P2.50mm_Horizontal Connector_JST:JST_XH_S8B-XH-A-1_1x08_P2.50mm_Horizontal @@ -2567,6 +2671,41 @@ Connector_JST:JST_ZE_SM13B-ZESS-TB_1x13-1MP_P1.50mm_Horizontal Connector_JST:JST_ZE_SM14B-ZESS-TB_1x14-1MP_P1.50mm_Horizontal Connector_JST:JST_ZE_SM15B-ZESS-TB_1x15-1MP_P1.50mm_Horizontal Connector_JST:JST_ZE_SM16B-ZESS-TB_1x16-1MP_P1.50mm_Horizontal +Connector_JST:JST_ZH_B10B-ZR-SM4-TF_1x10-1MP_P1.50mm_Vertical +Connector_JST:JST_ZH_B10B-ZR_1x10_P1.50mm_Vertical +Connector_JST:JST_ZH_B11B-ZR-SM4-TF_1x11-1MP_P1.50mm_Vertical +Connector_JST:JST_ZH_B11B-ZR_1x11_P1.50mm_Vertical +Connector_JST:JST_ZH_B12B-ZR-SM4-TF_1x12-1MP_P1.50mm_Vertical +Connector_JST:JST_ZH_B12B-ZR_1x12_P1.50mm_Vertical +Connector_JST:JST_ZH_B13B-ZR-SM4-TF_1x13-1MP_P1.50mm_Vertical +Connector_JST:JST_ZH_B2B-ZR-SM4-TF_1x02-1MP_P1.50mm_Vertical +Connector_JST:JST_ZH_B2B-ZR_1x02_P1.50mm_Vertical +Connector_JST:JST_ZH_B3B-ZR-SM4-TF_1x03-1MP_P1.50mm_Vertical +Connector_JST:JST_ZH_B3B-ZR_1x03_P1.50mm_Vertical +Connector_JST:JST_ZH_B4B-ZR-SM4-TF_1x04-1MP_P1.50mm_Vertical +Connector_JST:JST_ZH_B4B-ZR_1x04_P1.50mm_Vertical +Connector_JST:JST_ZH_B5B-ZR-SM4-TF_1x05-1MP_P1.50mm_Vertical +Connector_JST:JST_ZH_B5B-ZR_1x05_P1.50mm_Vertical +Connector_JST:JST_ZH_B6B-ZR-SM4-TF_1x06-1MP_P1.50mm_Vertical +Connector_JST:JST_ZH_B6B-ZR_1x06_P1.50mm_Vertical +Connector_JST:JST_ZH_B7B-ZR-SM4-TF_1x07-1MP_P1.50mm_Vertical +Connector_JST:JST_ZH_B7B-ZR_1x07_P1.50mm_Vertical +Connector_JST:JST_ZH_B8B-ZR-SM4-TF_1x08-1MP_P1.50mm_Vertical +Connector_JST:JST_ZH_B8B-ZR_1x08_P1.50mm_Vertical +Connector_JST:JST_ZH_B9B-ZR-SM4-TF_1x09-1MP_P1.50mm_Vertical +Connector_JST:JST_ZH_B9B-ZR_1x09_P1.50mm_Vertical +Connector_JST:JST_ZH_S10B-ZR-SM4A-TF_1x10-1MP_P1.50mm_Horizontal +Connector_JST:JST_ZH_S11B-ZR-SM4A-TF_1x11-1MP_P1.50mm_Horizontal +Connector_JST:JST_ZH_S12B-ZR-SM4A-TF_1x12-1MP_P1.50mm_Horizontal +Connector_JST:JST_ZH_S13B-ZR-SM4A-TF_1x13-1MP_P1.50mm_Horizontal +Connector_JST:JST_ZH_S2B-ZR-SM4A-TF_1x02-1MP_P1.50mm_Horizontal +Connector_JST:JST_ZH_S3B-ZR-SM4A-TF_1x03-1MP_P1.50mm_Horizontal +Connector_JST:JST_ZH_S4B-ZR-SM4A-TF_1x04-1MP_P1.50mm_Horizontal +Connector_JST:JST_ZH_S5B-ZR-SM4A-TF_1x05-1MP_P1.50mm_Horizontal +Connector_JST:JST_ZH_S6B-ZR-SM4A-TF_1x06-1MP_P1.50mm_Horizontal +Connector_JST:JST_ZH_S7B-ZR-SM4A-TF_1x07-1MP_P1.50mm_Horizontal +Connector_JST:JST_ZH_S8B-ZR-SM4A-TF_1x08-1MP_P1.50mm_Horizontal +Connector_JST:JST_ZH_S9B-ZR-SM4A-TF_1x09-1MP_P1.50mm_Horizontal Connector_Molex:Molex_CLIK-Mate_502382-0270_1x02-1MP_P1.25mm_Vertical Connector_Molex:Molex_CLIK-Mate_502382-0370_1x03-1MP_P1.25mm_Vertical Connector_Molex:Molex_CLIK-Mate_502382-0470_1x04-1MP_P1.25mm_Vertical @@ -3098,12 +3237,21 @@ Connector_Molex:Molex_Pico-EZmate_78171-0003_1x03-1MP_P1.20mm_Vertical Connector_Molex:Molex_Pico-EZmate_78171-0004_1x04-1MP_P1.20mm_Vertical Connector_Molex:Molex_Pico-EZmate_78171-0005_1x05-1MP_P1.20mm_Vertical Connector_Molex:Molex_Pico-EZmate_Slim_202656-0021_1x02-1MP_P1.20mm_Vertical +Connector_Molex:Molex_Pico-Lock_205338-0002_1x02-1MP_P2.00mm_Horizontal +Connector_Molex:Molex_Pico-Lock_205338-0004_1x04-1MP_P2.00mm_Horizontal +Connector_Molex:Molex_Pico-Lock_205338-0006_1x06-1MP_P2.00mm_Horizontal +Connector_Molex:Molex_Pico-Lock_205338-0008_1x08-1MP_P2.00mm_Horizontal +Connector_Molex:Molex_Pico-Lock_205338-0010_1x10-1MP_P2.00mm_Horizontal +Connector_Molex:Molex_Pico-Lock_504050-0291_1x02-1MP_P1.50mm_Horizontal +Connector_Molex:Molex_Pico-Lock_504050-0391_1x03-1MP_P1.50mm_Horizontal Connector_Molex:Molex_Pico-Lock_504050-0491_1x04-1MP_P1.50mm_Horizontal Connector_Molex:Molex_Pico-Lock_504050-0591_1x05-1MP_P1.50mm_Horizontal Connector_Molex:Molex_Pico-Lock_504050-0691_1x06-1MP_P1.50mm_Horizontal Connector_Molex:Molex_Pico-Lock_504050-0791_1x07-1MP_P1.50mm_Horizontal Connector_Molex:Molex_Pico-Lock_504050-0891_1x08-1MP_P1.50mm_Horizontal +Connector_Molex:Molex_Pico-Lock_504050-0991_1x09-1MP_P1.50mm_Horizontal Connector_Molex:Molex_Pico-Lock_504050-1091_1x10-1MP_P1.50mm_Horizontal +Connector_Molex:Molex_Pico-Lock_504050-1191_1x11-1MP_P1.50mm_Horizontal Connector_Molex:Molex_Pico-Lock_504050-1291_1x12-1MP_P1.50mm_Horizontal Connector_Molex:Molex_Pico-SPOX_87437-1443_1x14-P1.5mm_Vertical Connector_Molex:Molex_PicoBlade_53047-0210_1x02_P1.25mm_Vertical @@ -3444,6 +3592,7 @@ Connector_PCBEdge:Samtec_MECF-70-0_-L-DV_2x70_P1.27mm_Polarized_Edge Connector_PCBEdge:Samtec_MECF-70-0_-NP-L-DV_2x70_P1.27mm_Edge Connector_PCBEdge:SODIMM-200_1.8V_Card_edge Connector_PCBEdge:SODIMM-200_2.5V_Card_edge +Connector_PCBEdge:SODIMM-260_DDR4_H4.0-5.2_OrientationStd_Socket Connector_Phoenix_GMSTB:PhoenixContact_GMSTBA_2,5_10-G-7,62_1x10_P7.62mm_Horizontal Connector_Phoenix_GMSTB:PhoenixContact_GMSTBA_2,5_10-G_1x10_P7.50mm_Horizontal Connector_Phoenix_GMSTB:PhoenixContact_GMSTBA_2,5_11-G-7,62_1x11_P7.62mm_Horizontal @@ -6132,16 +6281,19 @@ Connector_RJ:RJ45_HALO_HFJ11-x2450HRL_Horizontal Connector_RJ:RJ45_Hanrun_HR911105A_Horizontal Connector_RJ:RJ45_Kycon_G7LX-A88S7-BP-xx_Horizontal Connector_RJ:RJ45_Molex_0855135013_Vertical +Connector_RJ:RJ45_Molex_9346520x_Horizontal Connector_RJ:RJ45_Ninigi_GE Connector_RJ:RJ45_OST_PJ012-8P8CX_Vertical Connector_RJ:RJ45_Plug_Metz_AJP92A8813 Connector_RJ:RJ45_Pulse_JK00177NL_Horizontal Connector_RJ:RJ45_Pulse_JK0654219NL_Horizontal Connector_RJ:RJ45_Pulse_JXD6-0001NL_Horizontal +Connector_RJ:RJ45_RCH_RC01937 Connector_RJ:RJ45_UDE_RB1-125B8G1A Connector_RJ:RJ45_Wuerth_74980111211_Horizontal Connector_RJ:RJ45_Wuerth_7499010001A_Horizontal Connector_RJ:RJ45_Wuerth_7499010121A_Horizontal +Connector_RJ:RJ45_Wuerth_7499010211A_Horizontal Connector_RJ:RJ45_Wuerth_7499111446_Horizontal Connector_RJ:RJ45_Wuerth_7499151120_Horizontal Connector_RJ:RJ9_Evercom_5301-440xxx_Horizontal @@ -6631,8 +6783,9 @@ Connector_Samtec_HPM_THT:Samtec_HPM-19-01-x-S_Straight_1x19_Pitch5.08mm Connector_Samtec_HPM_THT:Samtec_HPM-19-05-x-S_Straight_1x19_Pitch5.08mm Connector_Samtec_HSEC8:Samtec_HSEC8-109-01-X-DV-A-BL_2x09_P0.8mm_Pol04_Socket_WeldTabs_BoardLocks Connector_Samtec_HSEC8:Samtec_HSEC8-109-01-X-DV-A-WT_2x09_P0.8mm_Pol04_Socket_WeldTabs -Connector_Samtec_HSEC8:Samtec_HSEC8-109-01-X-DV-A_2x09_P0.8mm_Pol04_Socket_AlignmentPins -Connector_Samtec_HSEC8:Samtec_HSEC8-109-01-X-DV_2x09_P0.8mm_Pol04_Socket +Connector_Samtec_HSEC8:Samtec_HSEC8-109-X-X-DV-BL_2x09_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-109-X-X-DV_2x09_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-109-X-X-DV_2x09_P0.8mm_Wing_Edge Connector_Samtec_HSEC8:Samtec_HSEC8-110-01-X-DV-A-BL_2x10_P0.8mm_Socket_WeldTabs_BoardLocks Connector_Samtec_HSEC8:Samtec_HSEC8-110-01-X-DV-A-WT_2x10_P0.8mm_Socket_WeldTabs Connector_Samtec_HSEC8:Samtec_HSEC8-110-01-X-DV-A_2x10_P0.8mm_Socket_AlignmentPins @@ -6640,6 +6793,9 @@ Connector_Samtec_HSEC8:Samtec_HSEC8-110-01-X-DV_2x10_P0.8mm_Socket Connector_Samtec_HSEC8:Samtec_HSEC8-110-03-X-DV-A-WT_2x10_P0.8mm_Socket_WeldTabs Connector_Samtec_HSEC8:Samtec_HSEC8-110-03-X-DV-A_2x10_P0.8mm_Socket_AlignmentPins Connector_Samtec_HSEC8:Samtec_HSEC8-110-03-X-DV_2x10_P0.8mm_Socket +Connector_Samtec_HSEC8:Samtec_HSEC8-110-X-X-DV-BL_2x10_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-110-X-X-DV_2x10_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-110-X-X-DV_2x10_P0.8mm_Wing_Edge Connector_Samtec_HSEC8:Samtec_HSEC8-1100-01-X-DV-A-BL_2x100_P0.8mm_Pol32_Socket_WeldTabs_BoardLocks Connector_Samtec_HSEC8:Samtec_HSEC8-1100-01-X-DV-A-WT_2x100_P0.8mm_Pol32_Socket_WeldTabs Connector_Samtec_HSEC8:Samtec_HSEC8-1100-01-X-DV-A_2x100_P0.8mm_Pol32_Socket_AlignmentPins @@ -6647,10 +6803,16 @@ Connector_Samtec_HSEC8:Samtec_HSEC8-1100-01-X-DV_2x100_P0.8mm_Pol32_Socket Connector_Samtec_HSEC8:Samtec_HSEC8-1100-03-X-DV-A-WT_2x100_P0.8mm_Pol32_Socket_WeldTabs Connector_Samtec_HSEC8:Samtec_HSEC8-1100-03-X-DV-A_2x100_P0.8mm_Pol32_Socket_AlignmentPins Connector_Samtec_HSEC8:Samtec_HSEC8-1100-03-X-DV_2x100_P0.8mm_Pol32_Socket +Connector_Samtec_HSEC8:Samtec_HSEC8-1100-X-X-DV-BL_2x100_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-1100-X-X-DV_2x100_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-1100-X-X-DV_2x100_P0.8mm_Wing_Edge Connector_Samtec_HSEC8:Samtec_HSEC8-113-01-X-DV-A-BL_2x13_P0.8mm_Pol06_Socket_WeldTabs_BoardLocks Connector_Samtec_HSEC8:Samtec_HSEC8-113-01-X-DV-A-WT_2x13_P0.8mm_Pol06_Socket_WeldTabs Connector_Samtec_HSEC8:Samtec_HSEC8-113-01-X-DV-A_2x13_P0.8mm_Pol06_Socket_AlignmentPins Connector_Samtec_HSEC8:Samtec_HSEC8-113-01-X-DV_2x13_P0.8mm_Pol06_Socket +Connector_Samtec_HSEC8:Samtec_HSEC8-113-X-X-DV-BL_2x13_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-113-X-X-DV_2x13_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-113-X-X-DV_2x13_P0.8mm_Wing_Edge Connector_Samtec_HSEC8:Samtec_HSEC8-120-01-X-DV-A-BL_2x20_P0.8mm_Socket_WeldTabs_BoardLocks Connector_Samtec_HSEC8:Samtec_HSEC8-120-01-X-DV-A-WT_2x20_P0.8mm_Socket_WeldTabs Connector_Samtec_HSEC8:Samtec_HSEC8-120-01-X-DV-A_2x20_P0.8mm_Socket_AlignmentPins @@ -6658,10 +6820,16 @@ Connector_Samtec_HSEC8:Samtec_HSEC8-120-01-X-DV_2x20_P0.8mm_Socket Connector_Samtec_HSEC8:Samtec_HSEC8-120-03-X-DV-A-WT_2x20_P0.8mm_Socket_WeldTabs Connector_Samtec_HSEC8:Samtec_HSEC8-120-03-X-DV-A_2x20_P0.8mm_Socket_AlignmentPins Connector_Samtec_HSEC8:Samtec_HSEC8-120-03-X-DV_2x20_P0.8mm_Socket +Connector_Samtec_HSEC8:Samtec_HSEC8-120-X-X-DV-BL_2x20_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-120-X-X-DV_2x20_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-120-X-X-DV_2x20_P0.8mm_Wing_Edge Connector_Samtec_HSEC8:Samtec_HSEC8-125-01-X-DV-A-BL_2x25_P0.8mm_Pol06_Socket_WeldTabs_BoardLocks Connector_Samtec_HSEC8:Samtec_HSEC8-125-01-X-DV-A-WT_2x25_P0.8mm_Pol06_Socket_WeldTabs Connector_Samtec_HSEC8:Samtec_HSEC8-125-01-X-DV-A_2x25_P0.8mm_Pol06_Socket_AlignmentPins Connector_Samtec_HSEC8:Samtec_HSEC8-125-01-X-DV_2x25_P0.8mm_Pol06_Socket +Connector_Samtec_HSEC8:Samtec_HSEC8-125-X-X-DV-BL_2x25_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-125-X-X-DV_2x25_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-125-X-X-DV_2x25_P0.8mm_Wing_Edge Connector_Samtec_HSEC8:Samtec_HSEC8-130-01-X-DV-A-BL_2x30_P0.8mm_Socket_WeldTabs_BoardLocks Connector_Samtec_HSEC8:Samtec_HSEC8-130-01-X-DV-A-WT_2x30_P0.8mm_Socket_WeldTabs Connector_Samtec_HSEC8:Samtec_HSEC8-130-01-X-DV-A_2x30_P0.8mm_Socket_AlignmentPins @@ -6669,10 +6837,16 @@ Connector_Samtec_HSEC8:Samtec_HSEC8-130-01-X-DV_2x30_P0.8mm_Socket Connector_Samtec_HSEC8:Samtec_HSEC8-130-03-X-DV-A-WT_2x30_P0.8mm_Socket_WeldTabs Connector_Samtec_HSEC8:Samtec_HSEC8-130-03-X-DV-A_2x30_P0.8mm_Socket_AlignmentPins Connector_Samtec_HSEC8:Samtec_HSEC8-130-03-X-DV_2x30_P0.8mm_Socket +Connector_Samtec_HSEC8:Samtec_HSEC8-130-X-X-DV-BL_2x30_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-130-X-X-DV_2x30_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-130-X-X-DV_2x30_P0.8mm_Wing_Edge Connector_Samtec_HSEC8:Samtec_HSEC8-137-01-X-DV-A-BL_2x37_P0.8mm_Pol21_Socket_WeldTabs_BoardLocks Connector_Samtec_HSEC8:Samtec_HSEC8-137-01-X-DV-A-WT_2x37_P0.8mm_Pol21_Socket_WeldTabs Connector_Samtec_HSEC8:Samtec_HSEC8-137-01-X-DV-A_2x37_P0.8mm_Pol21_Socket_AlignmentPins Connector_Samtec_HSEC8:Samtec_HSEC8-137-01-X-DV_2x37_P0.8mm_Pol21_Socket +Connector_Samtec_HSEC8:Samtec_HSEC8-137-X-X-DV-BL_2x37_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-137-X-X-DV_2x37_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-137-X-X-DV_2x37_P0.8mm_Wing_Edge Connector_Samtec_HSEC8:Samtec_HSEC8-140-01-X-DV-A-BL_2x40_P0.8mm_Pol22_Socket_WeldTabs_BoardLocks Connector_Samtec_HSEC8:Samtec_HSEC8-140-01-X-DV-A-WT_2x40_P0.8mm_Pol22_Socket_WeldTabs Connector_Samtec_HSEC8:Samtec_HSEC8-140-01-X-DV-A_2x40_P0.8mm_Pol22_Socket_AlignmentPins @@ -6680,10 +6854,16 @@ Connector_Samtec_HSEC8:Samtec_HSEC8-140-01-X-DV_2x40_P0.8mm_Pol22_Socket Connector_Samtec_HSEC8:Samtec_HSEC8-140-03-X-DV-A-WT_2x40_P0.8mm_Pol22_Socket_WeldTabs Connector_Samtec_HSEC8:Samtec_HSEC8-140-03-X-DV-A_2x40_P0.8mm_Pol22_Socket_AlignmentPins Connector_Samtec_HSEC8:Samtec_HSEC8-140-03-X-DV_2x40_P0.8mm_Pol22_Socket +Connector_Samtec_HSEC8:Samtec_HSEC8-140-X-X-DV-BL_2x40_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-140-X-X-DV_2x40_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-140-X-X-DV_2x40_P0.8mm_Wing_Edge Connector_Samtec_HSEC8:Samtec_HSEC8-149-01-X-DV-A-BL_2x49_P0.8mm_Pol27_Socket_WeldTabs_BoardLocks Connector_Samtec_HSEC8:Samtec_HSEC8-149-01-X-DV-A-WT_2x49_P0.8mm_Pol27_Socket_WeldTabs Connector_Samtec_HSEC8:Samtec_HSEC8-149-01-X-DV-A_2x49_P0.8mm_Pol27_Socket_AlignmentPins Connector_Samtec_HSEC8:Samtec_HSEC8-149-01-X-DV_2x49_P0.8mm_Pol27_Socket +Connector_Samtec_HSEC8:Samtec_HSEC8-149-X-X-DV-BL_2x49_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-149-X-X-DV_2x49_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-149-X-X-DV_2x49_P0.8mm_Wing_Edge Connector_Samtec_HSEC8:Samtec_HSEC8-150-01-X-DV-A-BL_2x50_P0.8mm_Pol27_Socket_WeldTabs_BoardLocks Connector_Samtec_HSEC8:Samtec_HSEC8-150-01-X-DV-A-WT_2x50_P0.8mm_Pol27_Socket_WeldTabs Connector_Samtec_HSEC8:Samtec_HSEC8-150-01-X-DV-A_2x50_P0.8mm_Pol27_Socket_AlignmentPins @@ -6691,6 +6871,9 @@ Connector_Samtec_HSEC8:Samtec_HSEC8-150-01-X-DV_2x50_P0.8mm_Pol27_Socket Connector_Samtec_HSEC8:Samtec_HSEC8-150-03-X-DV-A-WT_2x50_P0.8mm_Pol27_Socket_WeldTabs Connector_Samtec_HSEC8:Samtec_HSEC8-150-03-X-DV-A_2x50_P0.8mm_Pol27_Socket_AlignmentPins Connector_Samtec_HSEC8:Samtec_HSEC8-150-03-X-DV_2x50_P0.8mm_Pol27_Socket +Connector_Samtec_HSEC8:Samtec_HSEC8-150-X-X-DV-BL_2x50_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-150-X-X-DV_2x50_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-150-X-X-DV_2x50_P0.8mm_Wing_Edge Connector_Samtec_HSEC8:Samtec_HSEC8-160-01-X-DV-A-BL_2x60_P0.8mm_Pol32_Socket_WeldTabs_BoardLocks Connector_Samtec_HSEC8:Samtec_HSEC8-160-01-X-DV-A-WT_2x60_P0.8mm_Pol32_Socket_WeldTabs Connector_Samtec_HSEC8:Samtec_HSEC8-160-01-X-DV-A_2x60_P0.8mm_Pol32_Socket_AlignmentPins @@ -6698,6 +6881,9 @@ Connector_Samtec_HSEC8:Samtec_HSEC8-160-01-X-DV_2x60_P0.8mm_Pol32_Socket Connector_Samtec_HSEC8:Samtec_HSEC8-160-03-X-DV-A-WT_2x60_P0.8mm_Pol32_Socket_WeldTabs Connector_Samtec_HSEC8:Samtec_HSEC8-160-03-X-DV-A_2x60_P0.8mm_Pol32_Socket_AlignmentPins Connector_Samtec_HSEC8:Samtec_HSEC8-160-03-X-DV_2x60_P0.8mm_Pol32_Socket +Connector_Samtec_HSEC8:Samtec_HSEC8-160-X-X-DV-BL_2x60_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-160-X-X-DV_2x60_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-160-X-X-DV_2x60_P0.8mm_Wing_Edge Connector_Samtec_HSEC8:Samtec_HSEC8-170-01-X-DV-A-BL_2x70_P0.8mm_Pol32_Socket_WeldTabs_BoardLocks Connector_Samtec_HSEC8:Samtec_HSEC8-170-01-X-DV-A-WT_2x70_P0.8mm_Pol32_Socket_WeldTabs Connector_Samtec_HSEC8:Samtec_HSEC8-170-01-X-DV-A_2x70_P0.8mm_Pol32_Socket_AlignmentPins @@ -6705,6 +6891,9 @@ Connector_Samtec_HSEC8:Samtec_HSEC8-170-01-X-DV_2x70_P0.8mm_Pol32_Socket Connector_Samtec_HSEC8:Samtec_HSEC8-170-03-X-DV-A-WT_2x70_P0.8mm_Pol32_Socket_WeldTabs Connector_Samtec_HSEC8:Samtec_HSEC8-170-03-X-DV-A_2x70_P0.8mm_Pol32_Socket_AlignmentPins Connector_Samtec_HSEC8:Samtec_HSEC8-170-03-X-DV_2x70_P0.8mm_Pol32_Socket +Connector_Samtec_HSEC8:Samtec_HSEC8-170-X-X-DV-BL_2x70_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-170-X-X-DV_2x70_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-170-X-X-DV_2x70_P0.8mm_Wing_Edge Connector_Samtec_HSEC8:Samtec_HSEC8-180-01-X-DV-A-BL_2x80_P0.8mm_Pol32_Socket_WeldTabs_BoardLocks Connector_Samtec_HSEC8:Samtec_HSEC8-180-01-X-DV-A-WT_2x80_P0.8mm_Pol32_Socket_WeldTabs Connector_Samtec_HSEC8:Samtec_HSEC8-180-01-X-DV-A_2x80_P0.8mm_Pol32_Socket_AlignmentPins @@ -6712,6 +6901,9 @@ Connector_Samtec_HSEC8:Samtec_HSEC8-180-01-X-DV_2x80_P0.8mm_Pol32_Socket Connector_Samtec_HSEC8:Samtec_HSEC8-180-03-X-DV-A-WT_2x80_P0.8mm_Pol32_Socket_WeldTabs Connector_Samtec_HSEC8:Samtec_HSEC8-180-03-X-DV-A_2x80_P0.8mm_Pol32_Socket_AlignmentPins Connector_Samtec_HSEC8:Samtec_HSEC8-180-03-X-DV_2x80_P0.8mm_Pol32_Socket +Connector_Samtec_HSEC8:Samtec_HSEC8-180-X-X-DV-BL_2x80_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-180-X-X-DV_2x80_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-180-X-X-DV_2x80_P0.8mm_Wing_Edge Connector_Samtec_HSEC8:Samtec_HSEC8-190-01-X-DV-A-BL_2x90_P0.8mm_Pol32_Socket_WeldTabs_BoardLocks Connector_Samtec_HSEC8:Samtec_HSEC8-190-01-X-DV-A-WT_2x90_P0.8mm_Pol32_Socket_WeldTabs Connector_Samtec_HSEC8:Samtec_HSEC8-190-01-X-DV-A_2x90_P0.8mm_Pol32_Socket_AlignmentPins @@ -6719,6 +6911,9 @@ Connector_Samtec_HSEC8:Samtec_HSEC8-190-01-X-DV_2x90_P0.8mm_Pol32_Socket Connector_Samtec_HSEC8:Samtec_HSEC8-190-03-X-DV-A-WT_2x90_P0.8mm_Pol32_Socket_WeldTabs Connector_Samtec_HSEC8:Samtec_HSEC8-190-03-X-DV-A_2x90_P0.8mm_Pol32_Socket_AlignmentPins Connector_Samtec_HSEC8:Samtec_HSEC8-190-03-X-DV_2x90_P0.8mm_Pol32_Socket +Connector_Samtec_HSEC8:Samtec_HSEC8-190-X-X-DV-BL_2x90_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-190-X-X-DV_2x90_P0.8mm_Edge +Connector_Samtec_HSEC8:Samtec_HSEC8-190-X-X-DV_2x90_P0.8mm_Wing_Edge Connector_Samtec_MicroMate:Samtec_T1M-02-X-S-RA_1x02-1MP_P1.0mm_Terminal_Horizontal Connector_Samtec_MicroMate:Samtec_T1M-02-X-S-V_1x02-1MP_P1.0mm_Terminal_Vertical Connector_Samtec_MicroMate:Samtec_T1M-02-X-SH-L_1x02-1MP_P1.0mm_Terminal_Horizontal_Latch @@ -6795,6 +6990,42 @@ Connector_Samtec_MicroMate:Samtec_T1M-20-X-S-RA_1x20-1MP_P1.0mm_Terminal_Horizon Connector_Samtec_MicroMate:Samtec_T1M-20-X-S-V_1x20-1MP_P1.0mm_Terminal_Vertical Connector_Samtec_MicroMate:Samtec_T1M-20-X-SH-L_1x20-1MP_P1.0mm_Terminal_Horizontal_Latch Connector_Samtec_MicroMate:Samtec_T1M-20-X-SV-L_1x20-1MP_P1.0mm_Terminal_Vertical_Latch +Connector_Samtec_MicroPower:Samtec_UMPS-02-XX.X-X-V-S-W_1x02-1MP_P2.0mm_Socket_WeldTab +Connector_Samtec_MicroPower:Samtec_UMPS-02-XX.X-X-V-S_1x02_P2.0mm_Socket +Connector_Samtec_MicroPower:Samtec_UMPS-03-XX.X-X-V-S-W_1x03-1MP_P2.0mm_Socket_WeldTab +Connector_Samtec_MicroPower:Samtec_UMPS-03-XX.X-X-V-S_1x03_P2.0mm_Socket +Connector_Samtec_MicroPower:Samtec_UMPS-04-XX.X-X-V-S-W_1x04-1MP_P2.0mm_Socket_WeldTab +Connector_Samtec_MicroPower:Samtec_UMPS-04-XX.X-X-V-S_1x04_P2.0mm_Socket +Connector_Samtec_MicroPower:Samtec_UMPS-05-XX.X-X-V-S-W_1x05-1MP_P2.0mm_Socket_WeldTab +Connector_Samtec_MicroPower:Samtec_UMPS-05-XX.X-X-V-S_1x05_P2.0mm_Socket +Connector_Samtec_MicroPower:Samtec_UMPS-06-XX.X-X-V-S-W_1x06-1MP_P2.0mm_Socket_WeldTab +Connector_Samtec_MicroPower:Samtec_UMPS-06-XX.X-X-V-S_1x06_P2.0mm_Socket +Connector_Samtec_MicroPower:Samtec_UMPS-07-XX.X-X-V-S-W_1x07-1MP_P2.0mm_Socket_WeldTab +Connector_Samtec_MicroPower:Samtec_UMPS-07-XX.X-X-V-S_1x07_P2.0mm_Socket +Connector_Samtec_MicroPower:Samtec_UMPS-08-XX.X-X-V-S-W_1x08-1MP_P2.0mm_Socket_WeldTab +Connector_Samtec_MicroPower:Samtec_UMPS-08-XX.X-X-V-S_1x08_P2.0mm_Socket +Connector_Samtec_MicroPower:Samtec_UMPS-09-XX.X-X-V-S-W_1x09-1MP_P2.0mm_Socket_WeldTab +Connector_Samtec_MicroPower:Samtec_UMPS-09-XX.X-X-V-S_1x09_P2.0mm_Socket +Connector_Samtec_MicroPower:Samtec_UMPS-10-XX.X-X-V-S-W_1x10-1MP_P2.0mm_Socket_WeldTab +Connector_Samtec_MicroPower:Samtec_UMPS-10-XX.X-X-V-S_1x10_P2.0mm_Socket +Connector_Samtec_MicroPower:Samtec_UMPT-02-XX.X-X-V-S-W_1x02-1MP_P2.0mm_Terminal_WeldTab +Connector_Samtec_MicroPower:Samtec_UMPT-02-XX.X-X-V-S_1x02_P2.0mm_Terminal +Connector_Samtec_MicroPower:Samtec_UMPT-03-XX.X-X-V-S-W_1x03-1MP_P2.0mm_Terminal_WeldTab +Connector_Samtec_MicroPower:Samtec_UMPT-03-XX.X-X-V-S_1x03_P2.0mm_Terminal +Connector_Samtec_MicroPower:Samtec_UMPT-04-XX.X-X-V-S-W_1x04-1MP_P2.0mm_Terminal_WeldTab +Connector_Samtec_MicroPower:Samtec_UMPT-04-XX.X-X-V-S_1x04_P2.0mm_Terminal +Connector_Samtec_MicroPower:Samtec_UMPT-05-XX.X-X-V-S-W_1x05-1MP_P2.0mm_Terminal_WeldTab +Connector_Samtec_MicroPower:Samtec_UMPT-05-XX.X-X-V-S_1x05_P2.0mm_Terminal +Connector_Samtec_MicroPower:Samtec_UMPT-06-XX.X-X-V-S-W_1x06-1MP_P2.0mm_Terminal_WeldTab +Connector_Samtec_MicroPower:Samtec_UMPT-06-XX.X-X-V-S_1x06_P2.0mm_Terminal +Connector_Samtec_MicroPower:Samtec_UMPT-07-XX.X-X-V-S-W_1x07-1MP_P2.0mm_Terminal_WeldTab +Connector_Samtec_MicroPower:Samtec_UMPT-07-XX.X-X-V-S_1x07_P2.0mm_Terminal +Connector_Samtec_MicroPower:Samtec_UMPT-08-XX.X-X-V-S-W_1x08-1MP_P2.0mm_Terminal_WeldTab +Connector_Samtec_MicroPower:Samtec_UMPT-08-XX.X-X-V-S_1x08_P2.0mm_Terminal +Connector_Samtec_MicroPower:Samtec_UMPT-09-XX.X-X-V-S-W_1x09-1MP_P2.0mm_Terminal_WeldTab +Connector_Samtec_MicroPower:Samtec_UMPT-09-XX.X-X-V-S_1x09_P2.0mm_Terminal +Connector_Samtec_MicroPower:Samtec_UMPT-10-XX.X-X-V-S-W_1x10-1MP_P2.0mm_Terminal_WeldTab +Connector_Samtec_MicroPower:Samtec_UMPT-10-XX.X-X-V-S_1x10_P2.0mm_Terminal Connector_SATA_SAS:SAS-mini_TEConnectivity_1888174_Vertical Connector_SATA_SAS:SATA_Amphenol_10029364-001LF_Horizontal Connector_Stocko:Stocko_MKS_1651-6-0-202_1x2_P2.50mm_Vertical @@ -6888,12 +7119,15 @@ Connector_USB:USB3_A_Plug_Wuerth_692112030100_Horizontal Connector_USB:USB3_A_Receptacle_Wuerth_692122030100 Connector_USB:USB3_Micro-B_Connfly_DS1104-01 Connector_USB:USB_A_CNCTech_1001-011-01101_Horizontal -Connector_USB:USB_A_CONNFLY_DS1095-WNR0 +Connector_USB:USB_A_Connfly_DS1095 +Connector_USB:USB_A_Connfly_DS1098_Horizontal Connector_USB:USB_A_CUI_UJ2-ADH-TH_Horizontal_Stacked +Connector_USB:USB_A_Kycon_KUSBX-AS1N-B_Horizontal Connector_USB:USB_A_Molex_105057_Vertical Connector_USB:USB_A_Molex_48037-2200_Horizontal Connector_USB:USB_A_Molex_67643_Horizontal Connector_USB:USB_A_Receptacle_GCT_USB1046 +Connector_USB:USB_A_Receptacle_XKB_U231-091N-4BLRA00-S Connector_USB:USB_A_Stewart_SS-52100-001_Horizontal Connector_USB:USB_A_TE_292303-7_Horizontal Connector_USB:USB_A_Wuerth_614004134726_Horizontal @@ -6909,8 +7143,11 @@ Connector_USB:USB_C_Receptacle_Amphenol_12401548E4-2A Connector_USB:USB_C_Receptacle_Amphenol_12401548E4-2A_CircularHoles Connector_USB:USB_C_Receptacle_Amphenol_12401610E4-2A Connector_USB:USB_C_Receptacle_Amphenol_12401610E4-2A_CircularHoles +Connector_USB:USB_C_Receptacle_Amphenol_12401948E412A +Connector_USB:USB_C_Receptacle_Amphenol_124019772112A Connector_USB:USB_C_Receptacle_CNCTech_C-ARA1-AK51X Connector_USB:USB_C_Receptacle_G-Switch_GT-USB-7010ASV +Connector_USB:USB_C_Receptacle_G-Switch_GT-USB-7025 Connector_USB:USB_C_Receptacle_G-Switch_GT-USB-7051x Connector_USB:USB_C_Receptacle_GCT_USB4085 Connector_USB:USB_C_Receptacle_GCT_USB4105-xx-A_16P_TopMnt_Horizontal @@ -6921,6 +7158,7 @@ Connector_USB:USB_C_Receptacle_GCT_USB4125-xx-x_6P_TopMnt_Horizontal Connector_USB:USB_C_Receptacle_GCT_USB4135-GF-A_6P_TopMnt_Horizontal Connector_USB:USB_C_Receptacle_HCTL_HC-TYPE-C-16P-01A Connector_USB:USB_C_Receptacle_HRO_TYPE-C-31-M-12 +Connector_USB:USB_C_Receptacle_HRO_TYPE-C-31-M-17 Connector_USB:USB_C_Receptacle_JAE_DX07S016JA1R1500 Connector_USB:USB_C_Receptacle_JAE_DX07S024WJ1R350 Connector_USB:USB_C_Receptacle_JAE_DX07S024WJ3R400 @@ -6932,6 +7170,7 @@ Connector_USB:USB_Micro-B_Amphenol_10103594-0001LF_Horizontal Connector_USB:USB_Micro-B_Amphenol_10104110_Horizontal Connector_USB:USB_Micro-B_Amphenol_10118193-0001LF_Horizontal Connector_USB:USB_Micro-B_Amphenol_10118193-0002LF_Horizontal +Connector_USB:USB_Micro-B_Amphenol_10118194-0001LF_Horizontal Connector_USB:USB_Micro-B_Amphenol_10118194_Horizontal Connector_USB:USB_Micro-B_GCT_USB3076-30-A Connector_USB:USB_Micro-B_Molex-105017-0001 @@ -6949,6 +7188,15 @@ Connector_USB:USB_Mini-B_Lumberg_2486_01_Horizontal Connector_USB:USB_Mini-B_Tensility_54-00023_Vertical Connector_USB:USB_Mini-B_Tensility_54-00023_Vertical_CircularHoles Connector_USB:USB_Mini-B_Wuerth_65100516121_Horizontal +Connector_Video:DVI-D_Molex_74320-4004_Horizontal +Connector_Video:DVI-I_Molex_74320-1004_Horizontal +Connector_Video:HDMI_A_Amphenol_10029449-x01xLF_Horizontal +Connector_Video:HDMI_A_Contact_Technology_HDMI-19APL2_Horizontal +Connector_Video:HDMI_A_Kycon_KDMIX-SL1-NS-WS-B15_VerticalRightAngle +Connector_Video:HDMI_A_Molex_208658-1001_Horizontal +Connector_Video:HDMI_Micro-D_Molex_46765-0x01 +Connector_Video:HDMI_Micro-D_Molex_46765-1x01 +Connector_Video:HDMI_Micro-D_Molex_46765-2x0x Connector_Wago:Wago_734-132_1x02_P3.50mm_Vertical Connector_Wago:Wago_734-133_1x03_P3.50mm_Vertical Connector_Wago:Wago_734-134_1x04_P3.50mm_Vertical @@ -7273,6 +7521,43 @@ Connector_Wire:SolderWire-2sqmm_1x06_P7.8mm_D2mm_OD3.9mm_Relief Connector_Wire:SolderWire-2sqmm_1x06_P7.8mm_D2mm_OD3.9mm_Relief2x Connector_Wire:SolderWirePad_1x01_SMD_1x2mm Connector_Wire:SolderWirePad_1x01_SMD_5x10mm +Connector_Wuerth:Wuerth_WR-PHD_610004243021_SMD_2x02_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_610006243021_SMD_2x03_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_610008243021_SMD_2x04_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_610010243021_SMD_2x05_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_610012243021_SMD_2x06_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_610016243021_SMD_2x08_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_610018243021_SMD_2x09_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_610020243021_SMD_2x10_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_610022243021_SMD_2x11_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_610024243021_SMD_2x12_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_610026243021_SMD_2x13_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_610032243021_SMD_2x16_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_610034243021_SMD_2x17_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_613004216921_Large_2x02_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_61300425721_Standard_2x02_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_613006216921_Large_2x03_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_61300625721_Standard_2x03_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_613008216921_Large_2x04_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_61300825721_Standard_2x04_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_613010216921_Large_2x05_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_61301025721_Standard_2x05_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_613012216921_Large_2x06_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_61301225721_Standard_2x06_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_613016216921_Large_2x08_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_61301625721_Standard_2x08_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_613018216921_Large_2x09_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_613020216921_Large_2x10_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_61302025721_Standard_2x10_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_613022216921_Large_2x11_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_613024216921_Large_2x12_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_61302425721_Standard_2x12_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_613026216921_Large_2x13_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_61302625721_Standard_2x13_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_613032216921_Large_2x16_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_61303225721_Standard_2x16_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_613034216921_Large_2x17_P2.54mm_Vertical +Connector_Wuerth:Wuerth_WR-PHD_61303425721_Standard_2x17_P2.54mm_Vertical Connector_Wuerth:Wuerth_WR-WTB_64800211622_1x02_P1.50mm_Vertical Connector_Wuerth:Wuerth_WR-WTB_64800311622_1x03_P1.50mm_Vertical Connector_Wuerth:Wuerth_WR-WTB_64800411622_1x04_P1.50mm_Vertical @@ -7284,9 +7569,13 @@ Connector_Wuerth:Wuerth_WR-WTB_64800911622_1x09_P1.50mm_Vertical Connector_Wuerth:Wuerth_WR-WTB_64801011622_1x10_P1.50mm_Vertical Converter_ACDC:Converter_ACDC_CUI_PBO-3-Sxx_THT_Vertical Converter_ACDC:Converter_ACDC_Hahn_HS-400xx_THT -Converter_ACDC:Converter_ACDC_HiLink_HLK-10Mxx -Converter_ACDC:Converter_ACDC_HiLink_HLK-5Mxx -Converter_ACDC:Converter_ACDC_HiLink_HLK-PMxx +Converter_ACDC:Converter_ACDC_Hi-Link_HLK-10Mxx +Converter_ACDC:Converter_ACDC_Hi-Link_HLK-12MxxA +Converter_ACDC:Converter_ACDC_Hi-Link_HLK-20Mxx +Converter_ACDC:Converter_ACDC_Hi-Link_HLK-2Mxx +Converter_ACDC:Converter_ACDC_Hi-Link_HLK-30Mxx +Converter_ACDC:Converter_ACDC_Hi-Link_HLK-5Mxx +Converter_ACDC:Converter_ACDC_Hi-Link_HLK-PMxx Converter_ACDC:Converter_ACDC_MeanWell_IRM-02-xx_SMD Converter_ACDC:Converter_ACDC_MeanWell_IRM-02-xx_THT Converter_ACDC:Converter_ACDC_MeanWell_IRM-03-xx_SMD @@ -7295,12 +7584,18 @@ Converter_ACDC:Converter_ACDC_MeanWell_IRM-05-xx_THT Converter_ACDC:Converter_ACDC_MeanWell_IRM-10-xx_THT Converter_ACDC:Converter_ACDC_MeanWell_IRM-20-xx_THT Converter_ACDC:Converter_ACDC_MeanWell_IRM-60-xx_THT +Converter_ACDC:Converter_ACDC_MeanWell_MFM-10-xx_THT +Converter_ACDC:Converter_ACDC_MeanWell_MFM-15-xx_THT Converter_ACDC:Converter_ACDC_Murata_BAC05SxxDC_THT Converter_ACDC:Converter_ACDC_RECOM_RAC01-xxSGB_THT Converter_ACDC:Converter_ACDC_RECOM_RAC04-xxSGx_THT Converter_ACDC:Converter_ACDC_RECOM_RAC05-xxSK_THT Converter_ACDC:Converter_ACDC_Recom_RAC20-xxDK_THT Converter_ACDC:Converter_ACDC_Recom_RAC20-xxSK_THT +Converter_ACDC:Converter_ACDC_TRACO_TMF_051xx_THT +Converter_ACDC:Converter_ACDC_TRACO_TMF_101xx_THT +Converter_ACDC:Converter_ACDC_TRACO_TMF_201xx_THT +Converter_ACDC:Converter_ACDC_TRACO_TMF_301xx_THT Converter_ACDC:Converter_ACDC_TRACO_TMG-15_THT Converter_ACDC:Converter_ACDC_TRACO_TMLM-04_THT Converter_ACDC:Converter_ACDC_TRACO_TMLM-05_THT @@ -7308,6 +7603,7 @@ Converter_ACDC:Converter_ACDC_TRACO_TMLM-10-20_THT Converter_ACDC:Converter_ACDC_TRACO_TPP-15-1xx-D_THT Converter_ACDC:Converter_ACDC_Vigortronix_VTX-214-010-xxx_THT Converter_ACDC:Converter_ACDC_Vigortronix_VTX-214-015-1xx_THT +Converter_ACDC:Converter_ACDC_ZETTLER_ZPI03Sxx00WC_THT Converter_DCDC:Converter_DCDC_Artesyn_ATA_SMD Converter_DCDC:Converter_DCDC_Bothhand_CFUDxxxx_THT Converter_DCDC:Converter_DCDC_Bothhand_CFUSxxxxEH_THT @@ -7320,6 +7616,7 @@ Converter_DCDC:Converter_DCDC_Cyntec_MUN12AD01-SH Converter_DCDC:Converter_DCDC_Cyntec_MUN12AD03-SH Converter_DCDC:Converter_DCDC_MeanWell_NID30_THT Converter_DCDC:Converter_DCDC_MeanWell_NID60_THT +Converter_DCDC:Converter_DCDC_MeanWell_NSD10_THT Converter_DCDC:Converter_DCDC_Murata_CRE1xxxxxx3C_THT Converter_DCDC:Converter_DCDC_Murata_CRE1xxxxxxDC_THT Converter_DCDC:Converter_DCDC_Murata_CRE1xxxxxxSC_THT @@ -7359,14 +7656,21 @@ Converter_DCDC:Converter_DCDC_TRACO_TDU1-xxxx_THT Converter_DCDC:Converter_DCDC_TRACO_TEA1-xxxxE_THT Converter_DCDC:Converter_DCDC_TRACO_TEA1-xxxxHI_THT Converter_DCDC:Converter_DCDC_TRACO_TEA1-xxxx_THT +Converter_DCDC:Converter_DCDC_TRACO_TEC3-24xxUI_THT Converter_DCDC:Converter_DCDC_TRACO_TEL12-xxxx_THT +Converter_DCDC:Converter_DCDC_TRACO_TEN10-110xxWIRH_THT Converter_DCDC:Converter_DCDC_TRACO_TEN10-xxxx_Dual_THT Converter_DCDC:Converter_DCDC_TRACO_TEN10-xxxx_Single_THT Converter_DCDC:Converter_DCDC_TRACO_TEN10-xxxx_THT +Converter_DCDC:Converter_DCDC_TRACO_TEN20-110xxWIRH_THT Converter_DCDC:Converter_DCDC_TRACO_TEN20-xxxx-N4_THT Converter_DCDC:Converter_DCDC_TRACO_TEN20-xxxx_THT +Converter_DCDC:Converter_DCDC_TRACO_TEN40-110xxWIRH_THT +Converter_DCDC:Converter_DCDC_TRACO_THB10-xxxx_Dual_THT +Converter_DCDC:Converter_DCDC_TRACO_THB10-xxxx_Single_THT Converter_DCDC:Converter_DCDC_TRACO_THD_15-xxxxWIN_THT Converter_DCDC:Converter_DCDC_TRACO_THN30-xxxx_THT +Converter_DCDC:Converter_DCDC_TRACO_THR40-72xxWI_THT Converter_DCDC:Converter_DCDC_TRACO_TMA-05xxD_12xxD_Dual_THT Converter_DCDC:Converter_DCDC_TRACO_TMA-05xxS_12xxS_Single_THT Converter_DCDC:Converter_DCDC_TRACO_TMA-15xxD_24xxD_Dual_THT @@ -7378,11 +7682,18 @@ Converter_DCDC:Converter_DCDC_TRACO_TMR-1-xxxx_Single_THT Converter_DCDC:Converter_DCDC_TRACO_TMR-1SM_SMD Converter_DCDC:Converter_DCDC_TRACO_TMR-2xxxxWI_THT Converter_DCDC:Converter_DCDC_TRACO_TMR-xxxx_THT +Converter_DCDC:Converter_DCDC_TRACO_TMR4-xxxxWI_THT Converter_DCDC:Converter_DCDC_TRACO_TMU3-05xx_12xx_THT Converter_DCDC:Converter_DCDC_TRACO_TMU3-24xx_THT +Converter_DCDC:Converter_DCDC_TRACO_TOS06-05SIL_THT +Converter_DCDC:Converter_DCDC_TRACO_TOS06-12SIL_THT +Converter_DCDC:Converter_DCDC_TRACO_TRA3-xxxx_THT Converter_DCDC:Converter_DCDC_TRACO_TRI1-xxxx_THT Converter_DCDC:Converter_DCDC_TRACO_TSR-1_THT +Converter_DCDC:Converter_DCDC_TRACO_TSR0.6-48xxWI_TSR0.6-48xxxWI_THT Converter_DCDC:Converter_DCDC_TRACO_TSR1-xxxxE_THT +Converter_DCDC:Converter_DCDC_TRACO_TSR2-24xxN_TSR2-24xxxN_THT +Converter_DCDC:Converter_DCDC_TRACO_TSR2-xxxx_THT Converter_DCDC:Converter_DCDC_XP_POWER-IA48xxD_THT Converter_DCDC:Converter_DCDC_XP_POWER-IA48xxS_THT Converter_DCDC:Converter_DCDC_XP_POWER-IAxxxxD_THT @@ -7465,6 +7776,7 @@ Crystal:Crystal_SMD_0603-2Pin_6.0x3.5mm Crystal:Crystal_SMD_0603-2Pin_6.0x3.5mm_HandSoldering Crystal:Crystal_SMD_0603-4Pin_6.0x3.5mm Crystal:Crystal_SMD_0603-4Pin_6.0x3.5mm_HandSoldering +Crystal:Crystal_SMD_1210-4Pin_1.2x1.0mm Crystal:Crystal_SMD_2012-2Pin_2.0x1.2mm Crystal:Crystal_SMD_2012-2Pin_2.0x1.2mm_HandSoldering Crystal:Crystal_SMD_2016-4Pin_2.0x1.6mm @@ -7641,6 +7953,7 @@ Diode_SMD:D_SMC-RM10_Universal_Handsoldering Diode_SMD:D_SMC Diode_SMD:D_SMC_Handsoldering Diode_SMD:D_SMF +Diode_SMD:D_SMP_DO-220AA Diode_SMD:D_SOD-110 Diode_SMD:D_SOD-123 Diode_SMD:D_SOD-123F @@ -7649,6 +7962,8 @@ Diode_SMD:D_SOD-323 Diode_SMD:D_SOD-323F Diode_SMD:D_SOD-323_HandSoldering Diode_SMD:D_SOD-523 +Diode_SMD:D_SOD-882 +Diode_SMD:D_SOD-882D Diode_SMD:D_SOD-923 Diode_SMD:D_TUMD2 Diode_SMD:Infineon_SG-WLL-2-3_0.58x0.28_P0.36mm @@ -7656,6 +7971,9 @@ Diode_SMD:Littelfuse_PolyZen-LS Diode_SMD:Nexperia_CFP3_SOD-123W Diode_SMD:Nexperia_DSN0603-2_0.6x0.3mm_P0.4mm Diode_SMD:Nexperia_DSN1608-2_1.6x0.8mm +Diode_SMD:OnSemi_751EP_SOIC-4_3.9x4.725mm_P2.54mm +Diode_SMD:ST_D_SMC +Diode_SMD:ST_QFN-2L_1.6x1.0mm Diode_SMD:Vishay_SMPA Diode_THT:Diode_Bridge_15.1x15.1x6.3mm_P10.9mm Diode_THT:Diode_Bridge_15.2x15.2x6.3mm_P10.9mm @@ -7788,6 +8106,7 @@ Display:EA_eDIPTFT57-XXX Display:EA_eDIPTFT70-ATC Display:EA_eDIPTFT70-XXX Display:EA_T123X-I2C +Display:ER-OLED0.42-1W_Folded Display:ERM19264 Display:HDSM-441B_HDSM-443B Display:HDSM-541B_HDSM-543B @@ -7802,6 +8121,7 @@ Display:HY1602E Display:LCD-016N002L Display:LM16255 Display:NHD-0420H1Z +Display:NHD-C0220BiZ-FSRGB Display:NHD-C0220BiZ Display:NHD-C12832A1Z-FSRGB Display:OLED-128O064D @@ -7873,6 +8193,7 @@ Filter:Filter_1109-5_1.1x0.9mm Filter:Filter_1411-5_1.4x1.1mm Filter:Filter_Bourns_SRF0905_6.0x9.2mm Filter:Filter_FILTERCON_1FPxx +Filter:Filter_KEMET_PZB300_24.0x12.5mm_P10.0mm Filter:Filter_Mini-Circuits_FV1206-1 Filter:Filter_Mini-Circuits_FV1206-4 Filter:Filter_Mini-Circuits_FV1206-5 @@ -8061,9 +8382,53 @@ Inductor_SMD:L_2512_6332Metric_Pad1.52x3.35mm_HandSolder Inductor_SMD:L_6.3x6.3_H3 Inductor_SMD:L_7.3x7.3_H3.5 Inductor_SMD:L_7.3x7.3_H4.5 +Inductor_SMD:L_Abracon_ASPI-0425 Inductor_SMD:L_Abracon_ASPI-0630LR Inductor_SMD:L_Abracon_ASPI-3012S Inductor_SMD:L_Abracon_ASPI-4030S +Inductor_SMD:L_Abracon_ASPIAIG-F4020 +Inductor_SMD:L_APV_ANR252010 +Inductor_SMD:L_APV_ANR252012 +Inductor_SMD:L_APV_ANR3010 +Inductor_SMD:L_APV_ANR3012 +Inductor_SMD:L_APV_ANR3015 +Inductor_SMD:L_APV_ANR4010 +Inductor_SMD:L_APV_ANR4012 +Inductor_SMD:L_APV_ANR4018 +Inductor_SMD:L_APV_ANR4020 +Inductor_SMD:L_APV_ANR4026 +Inductor_SMD:L_APV_ANR4030 +Inductor_SMD:L_APV_ANR5012 +Inductor_SMD:L_APV_ANR5020 +Inductor_SMD:L_APV_ANR5040 +Inductor_SMD:L_APV_ANR5045 +Inductor_SMD:L_APV_ANR6020 +Inductor_SMD:L_APV_ANR6028 +Inductor_SMD:L_APV_ANR6045 +Inductor_SMD:L_APV_ANR8040 +Inductor_SMD:L_APV_ANR8065 +Inductor_SMD:L_APV_APH0412 +Inductor_SMD:L_APV_APH0420 +Inductor_SMD:L_APV_APH0518 +Inductor_SMD:L_APV_APH0530 +Inductor_SMD:L_APV_APH0615 +Inductor_SMD:L_APV_APH0618 +Inductor_SMD:L_APV_APH0620 +Inductor_SMD:L_APV_APH0624 +Inductor_SMD:L_APV_APH0630 +Inductor_SMD:L_APV_APH0640 +Inductor_SMD:L_APV_APH0650 +Inductor_SMD:L_APV_APH0840 +Inductor_SMD:L_APV_APH0850 +Inductor_SMD:L_APV_APH1030 +Inductor_SMD:L_APV_APH1040 +Inductor_SMD:L_APV_APH1050 +Inductor_SMD:L_APV_APH1240 +Inductor_SMD:L_APV_APH1250 +Inductor_SMD:L_APV_APH1260 +Inductor_SMD:L_APV_APH1265 +Inductor_SMD:L_APV_APH1770 +Inductor_SMD:L_APV_APH2213 Inductor_SMD:L_AVX_LMLP07A7 Inductor_SMD:L_Bourns-SRN1060 Inductor_SMD:L_Bourns-SRN4018 @@ -8073,20 +8438,39 @@ Inductor_SMD:L_Bourns-SRR1005 Inductor_SMD:L_Bourns-SRU1028_10.0x10.0mm Inductor_SMD:L_Bourns-SRU8028_8.0x8.0mm Inductor_SMD:L_Bourns-SRU8043 +Inductor_SMD:L_Bourns_SDR0604 Inductor_SMD:L_Bourns_SDR1806 Inductor_SMD:L_Bourns_SRF1260 Inductor_SMD:L_Bourns_SRN6045TA Inductor_SMD:L_Bourns_SRN8040TA Inductor_SMD:L_Bourns_SRP1038C_10.0x10.0mm +Inductor_SMD:L_Bourns_SRP1050WA Inductor_SMD:L_Bourns_SRP1245A Inductor_SMD:L_Bourns_SRP1770TA_16.9x16.9mm Inductor_SMD:L_Bourns_SRP2313AA Inductor_SMD:L_Bourns_SRP5030T +Inductor_SMD:L_Bourns_SRP6060FA Inductor_SMD:L_Bourns_SRP7028A_7.3x6.6mm Inductor_SMD:L_Bourns_SRR1208_12.7x12.7mm Inductor_SMD:L_Bourns_SRR1210A Inductor_SMD:L_Bourns_SRR1260 Inductor_SMD:L_Bourns_SRU5016_5.2x5.2mm +Inductor_SMD:L_Cenker_CKCS201610 +Inductor_SMD:L_Cenker_CKCS252010 +Inductor_SMD:L_Cenker_CKCS252012 +Inductor_SMD:L_Cenker_CKCS3012 +Inductor_SMD:L_Cenker_CKCS3015 +Inductor_SMD:L_Cenker_CKCS4018 +Inductor_SMD:L_Cenker_CKCS4020 +Inductor_SMD:L_Cenker_CKCS4030 +Inductor_SMD:L_Cenker_CKCS5020 +Inductor_SMD:L_Cenker_CKCS5040 +Inductor_SMD:L_Cenker_CKCS6020 +Inductor_SMD:L_Cenker_CKCS6028 +Inductor_SMD:L_Cenker_CKCS6045 +Inductor_SMD:L_Cenker_CKCS8040 +Inductor_SMD:L_Cenker_CKCS8060 +Inductor_SMD:L_Cenker_CKCS8080 Inductor_SMD:L_Changjiang_FNR252010S Inductor_SMD:L_Changjiang_FNR252012S Inductor_SMD:L_Changjiang_FNR3010S @@ -8113,6 +8497,26 @@ Inductor_SMD:L_Changjiang_FNR6045S Inductor_SMD:L_Changjiang_FNR8040S Inductor_SMD:L_Changjiang_FNR8050S Inductor_SMD:L_Changjiang_FNR8065S +Inductor_SMD:L_Changjiang_FXL0412 +Inductor_SMD:L_Changjiang_FXL0420 +Inductor_SMD:L_Changjiang_FXL0518 +Inductor_SMD:L_Changjiang_FXL0530 +Inductor_SMD:L_Changjiang_FXL0615 +Inductor_SMD:L_Changjiang_FXL0618 +Inductor_SMD:L_Changjiang_FXL0624 +Inductor_SMD:L_Changjiang_FXL0630 +Inductor_SMD:L_Changjiang_FXL0640 +Inductor_SMD:L_Changjiang_FXL0650 +Inductor_SMD:L_Changjiang_FXL0840 +Inductor_SMD:L_Changjiang_FXL1030 +Inductor_SMD:L_Changjiang_FXL1040 +Inductor_SMD:L_Changjiang_FXL1050 +Inductor_SMD:L_Changjiang_FXL1340 +Inductor_SMD:L_Changjiang_FXL1350 +Inductor_SMD:L_Changjiang_FXL1360 +Inductor_SMD:L_Changjiang_FXL1365 +Inductor_SMD:L_Changjiang_FXL1770 +Inductor_SMD:L_Changjiang_FXL2213 Inductor_SMD:L_Chilisin_BMRA00040415 Inductor_SMD:L_Chilisin_BMRA00040420 Inductor_SMD:L_Chilisin_BMRA00050520 @@ -8135,10 +8539,47 @@ Inductor_SMD:L_Chilisin_BMRx00050512-B Inductor_SMD:L_Chilisin_BMRx00050515 Inductor_SMD:L_Chilisin_BMRx00060615 Inductor_SMD:L_Chilisin_BMRx00060630 +Inductor_SMD:L_Coilcraft_1515SQ-47N +Inductor_SMD:L_Coilcraft_1515SQ-68N +Inductor_SMD:L_Coilcraft_1515SQ-82N +Inductor_SMD:L_Coilcraft_2222SQ-111 +Inductor_SMD:L_Coilcraft_2222SQ-131 +Inductor_SMD:L_Coilcraft_2222SQ-161 +Inductor_SMD:L_Coilcraft_2222SQ-181 +Inductor_SMD:L_Coilcraft_2222SQ-221 +Inductor_SMD:L_Coilcraft_2222SQ-271 +Inductor_SMD:L_Coilcraft_2222SQ-301 +Inductor_SMD:L_Coilcraft_2222SQ-90N +Inductor_SMD:L_Coilcraft_2929SQ-331 +Inductor_SMD:L_Coilcraft_2929SQ-361 +Inductor_SMD:L_Coilcraft_2929SQ-391 +Inductor_SMD:L_Coilcraft_2929SQ-431 +Inductor_SMD:L_Coilcraft_2929SQ-501 Inductor_SMD:L_Coilcraft_LPS3010 Inductor_SMD:L_Coilcraft_LPS3314 Inductor_SMD:L_Coilcraft_LPS4018 +Inductor_SMD:L_Coilcraft_LPS4414 Inductor_SMD:L_Coilcraft_LPS5030 +Inductor_SMD:L_Coilcraft_MOS6020-XXX +Inductor_SMD:L_Coilcraft_MSS1038-XXX +Inductor_SMD:L_Coilcraft_MSS1038T-XXX +Inductor_SMD:L_Coilcraft_MSS1048-XXX +Inductor_SMD:L_Coilcraft_MSS1048T-XXX +Inductor_SMD:L_Coilcraft_MSS1210-XXX +Inductor_SMD:L_Coilcraft_MSS1210H-XXX +Inductor_SMD:L_Coilcraft_MSS1246-XXX +Inductor_SMD:L_Coilcraft_MSS1246H-XXX +Inductor_SMD:L_Coilcraft_MSS1246T-XXX +Inductor_SMD:L_Coilcraft_MSS1260-XXX +Inductor_SMD:L_Coilcraft_MSS1260H-XXX +Inductor_SMD:L_Coilcraft_MSS1260T-XXX +Inductor_SMD:L_Coilcraft_MSS1278-XXX +Inductor_SMD:L_Coilcraft_MSS1278H-XXX +Inductor_SMD:L_Coilcraft_MSS1278T-XXX +Inductor_SMD:L_Coilcraft_MSS1514V-XXX +Inductor_SMD:L_Coilcraft_MSS1583-XXX +Inductor_SMD:L_Coilcraft_MSS1812T-XXX +Inductor_SMD:L_Coilcraft_MSS7348-XXX Inductor_SMD:L_Coilcraft_XAL1010-XXX Inductor_SMD:L_Coilcraft_XAL1030-XXX Inductor_SMD:L_Coilcraft_XAL1060-XXX @@ -8223,6 +8664,8 @@ Inductor_SMD:L_Ferrocore_DLG-0703 Inductor_SMD:L_Ferrocore_DLG-0705 Inductor_SMD:L_Ferrocore_DLG-1004 Inductor_SMD:L_Ferrocore_DLG-1005 +Inductor_SMD:L_KOHERelec_MDA5030 +Inductor_SMD:L_KOHERelec_MDA7030 Inductor_SMD:L_Murata_DEM35xxC Inductor_SMD:L_Murata_DFE201610P Inductor_SMD:L_Murata_LQH2MCNxxxx02_2.0x1.6mm @@ -8264,8 +8707,12 @@ Inductor_SMD:L_Neosid_SMs85 Inductor_SMD:L_Neosid_SMs95_SMs95p Inductor_SMD:L_Pulse_P059x Inductor_SMD:L_Pulse_PA4320 +Inductor_SMD:L_Pulse_PA4332 +Inductor_SMD:L_Pulse_PA4340 +Inductor_SMD:L_Pulse_PA4341 Inductor_SMD:L_Pulse_PA4344 Inductor_SMD:L_Pulse_PA4349 +Inductor_SMD:L_Pulse_PA5402 Inductor_SMD:L_Sagami_CER1242B Inductor_SMD:L_Sagami_CER1257B Inductor_SMD:L_Sagami_CER1277B @@ -8273,6 +8720,7 @@ Inductor_SMD:L_Sagami_CWR1242C Inductor_SMD:L_Sagami_CWR1257C Inductor_SMD:L_Sagami_CWR1277C Inductor_SMD:L_SigTra_SC3316F +Inductor_SMD:L_SOREDE_SNR.1050_10x10x5mm Inductor_SMD:L_Sumida_CDMC6D28_7.25x6.5mm Inductor_SMD:L_Sumida_CR75 Inductor_SMD:L_Sunlord_MWSA0402S @@ -8364,6 +8812,13 @@ Inductor_SMD:L_Sunlord_SWPA8040S Inductor_SMD:L_Sunlord_SWRB1204S Inductor_SMD:L_Sunlord_SWRB1205S Inductor_SMD:L_Sunlord_SWRB1207S +Inductor_SMD:L_SXN_SMDRI124 +Inductor_SMD:L_SXN_SMDRI125 +Inductor_SMD:L_SXN_SMDRI127 +Inductor_SMD:L_SXN_SMDRI62 +Inductor_SMD:L_SXN_SMDRI64 +Inductor_SMD:L_SXN_SMDRI73 +Inductor_SMD:L_SXN_SMDRI74 Inductor_SMD:L_TaiTech_TMPC1265_13.5x12.5mm Inductor_SMD:L_Taiyo-Yuden_BK_Array_1206_3216Metric Inductor_SMD:L_Taiyo-Yuden_MD-1616 @@ -8632,6 +9087,7 @@ Inductor_THT:L_Radial_D12.5mm_P7.00mm_Fastron_09HCP Inductor_THT:L_Radial_D12.5mm_P9.00mm_Fastron_09HCP Inductor_THT:L_Radial_D13.5mm_P7.00mm_Fastron_09HCP Inductor_THT:L_Radial_D14.2mm_P10.00mm_Neosid_SD14 +Inductor_THT:L_Radial_D16.0mm_P10.00mm_Panasonic_15E-L Inductor_THT:L_Radial_D16.8mm_P11.43mm_Vishay_IHB-1 Inductor_THT:L_Radial_D16.8mm_P12.07mm_Vishay_IHB-1 Inductor_THT:L_Radial_D16.8mm_P12.70mm_Vishay_IHB-1 @@ -8822,14 +9278,18 @@ LED_SMD:LED_Cree-XQ LED_SMD:LED_Cree-XQ_HandSoldering LED_SMD:LED_CSP_Samsung_LH181B_2.36x2.36mm LED_SMD:LED_Dialight_591 +LED_SMD:LED_Everlight-SMD3528_3.5x2.8mm_67-21ST +LED_SMD:LED_Inolux_IN-P55TATRGB_PLCC6_5.0x5.5mm_P1.8mm LED_SMD:LED_Inolux_IN-PI554FCH_PLCC4_5.0x5.0mm_P3.2mm LED_SMD:LED_Kingbright_AAA3528ESGCT +LED_SMD:LED_Kingbright_APA1606_1.6x0.6mm_Horizontal LED_SMD:LED_Kingbright_APDA3020VBCD LED_SMD:LED_Kingbright_APFA3010_3x1.5mm_Horizontal LED_SMD:LED_Kingbright_APHBM2012_2x1.25mm LED_SMD:LED_Kingbright_KPA-3010_3x2x1mm LED_SMD:LED_Kingbright_KPBD-3224 LED_SMD:LED_LiteOn_LTST-C19HE1WT +LED_SMD:LED_LiteOn_LTST-C235KGKRKT LED_SMD:LED_LiteOn_LTST-C295K_1.6x0.8mm LED_SMD:LED_LiteOn_LTST-E563C_PLCC4_5.0x5.0mm_P3.2mm LED_SMD:LED_LiteOn_LTST-E563C_PLCC4_5.0x5.0mm_P3.2mm_HandSoldering @@ -8839,6 +9299,7 @@ LED_SMD:LED_Lumex_SML-LX0404SIUPGUSB LED_SMD:LED_Luminus_MP-3030-1100_3.0x3.0mm LED_SMD:LED_miniPLCC_2315 LED_SMD:LED_miniPLCC_2315_Handsoldering +LED_SMD:LED_OPSCO_SK6812_PLCC4_5.0x5.0mm_P3.1mm LED_SMD:LED_Osram_Lx_P47F_D2mm_ReverseMount LED_SMD:LED_PLCC-2_3.4x3.0mm_AK LED_SMD:LED_PLCC-2_3.4x3.0mm_KA @@ -8851,14 +9312,17 @@ LED_SMD:LED_RGB_5050-6 LED_SMD:LED_RGB_Cree-PLCC-6_6x5mm_P2.1mm LED_SMD:LED_RGB_Everlight_EASV3015RGBA0_Horizontal LED_SMD:LED_RGB_Getian_GT-P6PRGB4303 +LED_SMD:LED_RGB_Lumex_SML-LXT0805SIUGUBW LED_SMD:LED_RGB_PLCC-6 LED_SMD:LED_RGB_Wuerth-PLCC4_3.2x2.8mm_150141M173100 +LED_SMD:LED_RGB_Wuerth_150080M153000 LED_SMD:LED_ROHM_SMLVN6 LED_SMD:LED_SK6805_PLCC4_2.4x2.7mm_P1.3mm LED_SMD:LED_SK6812MINI_PLCC4_3.5x3.5mm_P1.75mm LED_SMD:LED_SK6812_EC15_1.5x1.5mm LED_SMD:LED_SK6812_PLCC4_5.0x5.0mm_P3.2mm LED_SMD:LED_WS2812B-2020_PLCC4_2.0x2.0mm +LED_SMD:LED_WS2812B-Mini_PLCC4_3.5x3.5mm LED_SMD:LED_WS2812B_PLCC4_5.0x5.0mm_P3.2mm LED_SMD:LED_WS2812_PLCC6_5.0x5.0mm_P1.6mm LED_SMD:LED_Wurth_150044M155260 @@ -8925,7 +9389,6 @@ LED_THT:LED_D8.0mm LED_THT:LED_Oval_W5.2mm_H3.8mm LED_THT:LED_Rectangular_W3.0mm_H2.0mm LED_THT:LED_Rectangular_W3.9mm_H1.8mm -LED_THT:LED_Rectangular_W3.9mm_H1.8mm_FlatTop LED_THT:LED_Rectangular_W3.9mm_H1.9mm LED_THT:LED_Rectangular_W5.0mm_H2.0mm-3Pins LED_THT:LED_Rectangular_W5.0mm_H2.0mm @@ -8969,19 +9432,29 @@ Module:Arduino_UNO_R3_WithMountingHoles Module:BeagleBoard_PocketBeagle Module:Carambola2 Module:Electrosmith_Daisy_Seed +Module:Flipper_Zero_Angled +Module:Flipper_Zero_Straight +Module:Google_Coral_SMT_TPU_Module Module:Maple_Mini Module:Olimex_MOD-WIFI-ESP8266-DEV Module:Onion_Omega2+ Module:Onion_Omega2S Module:Pololu_Breakout-16_15.2x20.3mm +Module:RaspberryPi_Pico_Common_SMD +Module:RaspberryPi_Pico_Common_THT +Module:RaspberryPi_Pico_Common_Unspecified +Module:RaspberryPi_Pico_SMD +Module:RaspberryPi_Pico_SMD_HandSolder +Module:RaspberryPi_Pico_W_SMD +Module:RaspberryPi_Pico_W_SMD_HandSolder Module:Raspberry_Pi_Zero_Socketed_THT_FaceDown_MountingHoles Module:Sipeed-M1 +Module:Sipeed-M1W Module:ST_Morpho_Connector_144_STLink Module:ST_Morpho_Connector_144_STLink_MountingHoles Module:Texas_EUK_R-PDSS-T7_THT Module:Texas_EUS_R-PDSS-T5_THT Module:Texas_EUW_R-PDSS-T7_THT -Module:WEMOS_D1_mini_light Motors:Vybronics_VZ30C1T8219732L MountingEquipment:DINRailAdapter_3xM3_PhoenixContact_1201578 MountingHole:MountingHole_2.1mm @@ -9150,6 +9623,7 @@ MountingHole:MountingHole_8.4mm_M8_Pad MountingHole:MountingHole_8.4mm_M8_Pad_TopBottom MountingHole:MountingHole_8.4mm_M8_Pad_TopOnly MountingHole:MountingHole_8.4mm_M8_Pad_Via +MountingHole:ToolingHole_1.152mm Mounting_Wuerth:Mounting_Wuerth_WA-SMSE-ExternalM3_H10mm_9771100360 Mounting_Wuerth:Mounting_Wuerth_WA-SMSE-ExternalM3_H11mm_9771110360 Mounting_Wuerth:Mounting_Wuerth_WA-SMSE-ExternalM3_H12mm_9771120360 @@ -9313,6 +9787,8 @@ Mounting_Wuerth:Mounting_Wuerth_WA-SMST-4.5mm_H6mm_9774060982 Mounting_Wuerth:Mounting_Wuerth_WA-SMST-4.5mm_H7mm_9774070982 Mounting_Wuerth:Mounting_Wuerth_WA-SMST-4.5mm_H8mm_9774080982 Mounting_Wuerth:Mounting_Wuerth_WA-SMST-4.5mm_H9mm_9774090982 +Mounting_Wuerth:Mounting_Wuerth_WP-SMRA-D3.3mm_L7mm_7466300_Horizontal +Mounting_Wuerth:Mounting_Wuerth_WP-SMRA-M3_L7mm_7466303_Horizontal NetTie:NetTie-2_SMD_Pad0.5mm NetTie:NetTie-2_SMD_Pad2.0mm NetTie:NetTie-2_THT_Pad0.3mm @@ -9325,34 +9801,20 @@ NetTie:NetTie-4_SMD_Pad0.5mm NetTie:NetTie-4_SMD_Pad2.0mm NetTie:NetTie-4_THT_Pad0.3mm NetTie:NetTie-4_THT_Pad1.0mm -Obsolete:Fiducial/Fiducial_1mm_Dia_2.54mm_Outer_CopperBottom -Obsolete:Fiducial/Fiducial_1mm_Dia_2.54mm_Outer_CopperTop -Obsolete:Fiducial/Fiducial_classic_big_CopperBottom_Type1 -Obsolete:Fiducial/Fiducial_classic_big_CopperBottom_Type2 -Obsolete:Fiducial/Fiducial_classic_big_CopperTop_Type1 -Obsolete:Fiducial/Fiducial_classic_big_CopperTop_Type2 -Obsolete:Fiducial/Fiducial_classic_big_SilkscreenTop_Type1 -Obsolete:Fiducial/Fiducial_classic_big_SilkscreenTop_Type2 -Obsolete:Fiducial/Fiducial_classic_Small_CopperBottom_Type1 -Obsolete:Fiducial/Fiducial_classic_Small_CopperBottom_Type2 -Obsolete:Fiducial/Fiducial_classic_Small_CopperTop_Type1 -Obsolete:Fiducial/Fiducial_classic_Small_CopperTop_Type2 -Obsolete:Fiducial/Fiducial_classic_Small_SilkscreenTop_Type1 -Obsolete:Fiducial/Fiducial_classic_Small_SilkscreenTop_Type2 -Obsolete:Fiducial/Fiducial_Modern_CopperBottom -Obsolete:Fiducial/Fiducial_Modern_CopperTop -Obsolete:Fiducial/Fiducial_Modern_SilkscreenTop OptoDevice:ADNS-9800 OptoDevice:AGILENT_HFBR-152x OptoDevice:AGILENT_HFBR-252x OptoDevice:AMS_TSL2550_SMD +OptoDevice:AMS_TSL25911FN OptoDevice:Broadcom_AFBR-16xxZ_Horizontal OptoDevice:Broadcom_AFBR-16xxZ_Tilted OptoDevice:Broadcom_AFBR-16xxZ_Vertical +OptoDevice:Broadcom_APDS-9160-003 OptoDevice:Broadcom_APDS-9301 OptoDevice:Broadcom_DFN-6_2x2mm_P0.65mm OptoDevice:Broadcom_LGA-8_2x2mm_P0.53mm OptoDevice:Broadcom_LGA-8_2x2mm_P0.5mm +OptoDevice:Everlight_IRM-H6xxT OptoDevice:Everlight_ITR1201SR10AR OptoDevice:Everlight_ITR8307 OptoDevice:Everlight_ITR8307F43 @@ -9415,6 +9877,7 @@ OptoDevice:Panasonic_APV-AQY_SSOP-4_4.45x2.65mm_P1.27mm OptoDevice:PerkinElmer_VTL5C OptoDevice:PerkinElmer_VTL5Cx2 OptoDevice:Renesas_DFN-6_1.5x1.6mm_P0.5mm +OptoDevice:Rohm_RPR-0720 OptoDevice:R_LDR_10x8.5mm_P7.6mm_Vertical OptoDevice:R_LDR_11x9.4mm_P8.2mm_Vertical OptoDevice:R_LDR_12x10.8mm_P9.0mm_Vertical @@ -9426,12 +9889,12 @@ OptoDevice:R_LDR_7x6mm_P5.1mm_Vertical OptoDevice:R_LDR_D13.8mm_P9.0mm_Vertical OptoDevice:R_LDR_D20mm_P17.5mm_Vertical OptoDevice:R_LDR_D6.4mm_P3.4mm_Vertical +OptoDevice:Sharp_GP2S700HCP OptoDevice:Sharp_GP2Y0A41SK0F OptoDevice:Sharp_IS471F OptoDevice:Sharp_IS485 OptoDevice:Siemens_SFH900 OptoDevice:ST_VL53L0X -OptoDevice:ST_VL53L1X OptoDevice:Toshiba_TORX170_TORX173_TORX193_TORX194 OptoDevice:Toshiba_TOTX170_TOTX173_TOTX193_TOTX194 OptoDevice:Vishay_CAST-3Pin @@ -9439,6 +9902,7 @@ OptoDevice:Vishay_CNY70 OptoDevice:Vishay_MINICAST-3Pin OptoDevice:Vishay_MINIMOLD-3Pin OptoDevice:Vishay_MOLD-3Pin +OptoDevice:Vishay_TCRT5000 Oscillator:Oscillator_DIP-14 Oscillator:Oscillator_DIP-14_LargePads Oscillator:Oscillator_DIP-8 @@ -9500,18 +9964,25 @@ Oscillator:Oscillator_SMD_SI570_SI571_Standard Oscillator:Oscillator_SMD_Silicon_Labs_LGA-6_2.5x3.2mm_P1.25mm Oscillator:Oscillator_SMD_SiTime_PQFD-6L_3.2x2.5mm Oscillator:Oscillator_SMD_SiTime_SiT9121-6Pin_3.2x2.5mm +Oscillator:Oscillator_SMD_SiT_PQFN-4Pin_2.0x1.6mm +Oscillator:Oscillator_SMD_SiT_PQFN-4Pin_2.5x2.0mm +Oscillator:Oscillator_SMD_SiT_PQFN-4Pin_3.2x2.5mm +Oscillator:Oscillator_SMD_SiT_PQFN-4Pin_5.0x3.2mm +Oscillator:Oscillator_SMD_SiT_PQFN-4Pin_7.0x5.0mm Oscillator:Oscillator_SMD_TCXO_G158 Oscillator:Oscillator_SMD_TXC_7C-4Pin_5.0x3.2mm Oscillator:Oscillator_SMD_TXC_7C-4Pin_5.0x3.2mm_HandSoldering +Package_BGA:Alliance_TFBGA-36_6x8mm_Layout6x8_P0.75mm Package_BGA:Alliance_TFBGA-54_8x8mm_Layout9x9_P0.8mm -Package_BGA:Analog_BGA-209_9.5x16mm_Layout11x19_P0.8mm_Ball0.5mm_Pad0.4mm -Package_BGA:Analog_BGA-28_4.0x6.25mm_Layout4x7_P0.8mm_Ball0.45mm_Pad0.4 -Package_BGA:Analog_BGA-49_6.25x6.25mm_Layout7x7_P0.8mm_Ball0.5mm_Pad0.4mm +Package_BGA:Analog_BGA-165_11.9x16mm_Layout11x15_P1.0mm +Package_BGA:Analog_BGA-209_9.5x16mm_Layout11x19_P0.8mm +Package_BGA:Analog_BGA-28_4x6.25mm_Layout4x7_P0.8mm +Package_BGA:Analog_BGA-49_6.25x6.25mm_Layout7x7_P0.8mm +Package_BGA:Analog_BGA-77_9x15mm_Layout7x11_P1.27mm Package_BGA:BGA-100_11.0x11.0mm_Layout10x10_P1.0mm_Ball0.5mm_Pad0.4mm_NSMD Package_BGA:BGA-100_6.0x6.0mm_Layout11x11_P0.5mm_Ball0.3mm_Pad0.25mm_NSMD Package_BGA:BGA-1023_33.0x33.0mm_Layout32x32_P1.0mm Package_BGA:BGA-1156_35.0x35.0mm_Layout34x34_P1.0mm -Package_BGA:BGA-121_12.0x12.0mm_Layout11x11_P1.0mm Package_BGA:BGA-121_9.0x9.0mm_Layout11x11_P0.8mm_Ball0.4mm_Pad0.35mm_NSMD Package_BGA:BGA-1295_37.5x37.5mm_Layout36x36_P1.0mm Package_BGA:BGA-132_12x18mm_Layout11x17_P1.0mm @@ -9521,13 +9992,14 @@ Package_BGA:BGA-152_14x18mm_Layout13x17_P0.5mm Package_BGA:BGA-153_8.0x8.0mm_Layout15x15_P0.5mm_Ball0.3mm_Pad0.25mm_NSMD Package_BGA:BGA-169_11.0x11.0mm_Layout13x13_P0.8mm_Ball0.5mm_Pad0.4mm_NSMD Package_BGA:BGA-16_1.92x1.92mm_Layout4x4_P0.5mm -Package_BGA:BGA-200_10.0x14.5mm_Layout12x22_P0.80x0.65mm +Package_BGA:BGA-196_15x15mm_Layout14x14_P1.0mm +Package_BGA:BGA-200_10x14.5mm_Layout12x22_P0.8x0.65mm Package_BGA:BGA-256_11.0x11.0mm_Layout20x20_P0.5mm_Ball0.3mm_Pad0.25mm_NSMD Package_BGA:BGA-256_14.0x14.0mm_Layout16x16_P0.8mm_Ball0.45mm_Pad0.32mm_NSMD Package_BGA:BGA-256_17.0x17.0mm_Layout16x16_P1.0mm_Ball0.5mm_Pad0.4mm_NSMD Package_BGA:BGA-25_6.35x6.35mm_Layout5x5_P1.27mm -Package_BGA:BGA-324_15.0x15.0mm_Layout18x18_P0.8mm_Ball0.45mm_Pad0.4mm_NSMD Package_BGA:BGA-324_15.0x15.0mm_Layout18x18_P0.8mm_Ball0.5mm_Pad0.4mm_NSMD +Package_BGA:BGA-324_15x15mm_Layout18x18_P0.8mm Package_BGA:BGA-324_19.0x19.0mm_Layout18x18_P1.0mm_Ball0.5mm_Pad0.4mm_NSMD Package_BGA:BGA-352_35.0x35.0mm_Layout26x26_P1.27mm Package_BGA:BGA-36_3.396x3.466mm_Layout6x6_P0.4mm_Ball0.25mm_Pad0.2mm_NSMD @@ -9535,7 +10007,7 @@ Package_BGA:BGA-400_21.0x21.0mm_Layout20x20_P1.0mm Package_BGA:BGA-484_23.0x23.0mm_Layout22x22_P1.0mm Package_BGA:BGA-48_8.0x9.0mm_Layout6x8_P0.8mm Package_BGA:BGA-529_19x19mm_Layout23x23_P0.8mm -Package_BGA:BGA-624_21.0x21.0mm_Layout25x25_P0.8mm +Package_BGA:BGA-624_21x21mm_Layout25x25_P0.8mm Package_BGA:BGA-625_21.0x21.0mm_Layout25x25_P0.8mm Package_BGA:BGA-64_9.0x9.0mm_Layout10x10_P0.8mm Package_BGA:BGA-672_27.0x27.0mm_Layout26x26_P1.0mm_Ball0.6mm_Pad0.5mm_NSMD @@ -9545,23 +10017,30 @@ Package_BGA:BGA-81_4.496x4.377mm_Layout9x9_P0.4mm_Ball0.25mm_Pad0.2mm_NSMD Package_BGA:BGA-90_8.0x13.0mm_Layout2x3x15_P0.8mm Package_BGA:BGA-96_9.0x13.0mm_Layout2x3x16_P0.8mm Package_BGA:BGA-9_1.6x1.6mm_Layout3x3_P0.5mm +Package_BGA:EPC_BGA-4_0.9x0.9mm_Layout2x2_P0.45mm Package_BGA:FB-BGA-484_23.0x23.0mm_Layout22x22_P1.0mm Package_BGA:FBGA-78_7.5x11mm_Layout2x3x13_P0.8mm Package_BGA:Fujitsu_WLP-15_2.28x3.092mm_Layout3x5_P0.4mm -Package_BGA:Infineon_LFBGA-292_17x17mm_Layout20x20_P0.8mm_Ball0.5mm_Pad0.35 -Package_BGA:Lattice_caBGA-381_17.0x17.0mm_Layout20x20_P0.8mm_Ball0.4mm_Pad0.4mm_NSMD -Package_BGA:Lattice_caBGA-381_17.0x17.0mm_Layout20x20_P0.8mm_Ball0.4mm_Pad0.6mm_SMD -Package_BGA:Lattice_caBGA-756_27.0x27.0mm_Layout32x32_P0.8mm +Package_BGA:Infineon_LFBGA-292_17x17mm_Layout20x20_P0.8mm +Package_BGA:Infineon_TFBGA-48_6x10mm_Layout6x8_P0.75mm +Package_BGA:Lattice_caBGA-381_17x17mm_Layout20x20_P0.8mm +Package_BGA:Lattice_caBGA-381_17x17mm_Layout20x20_P0.8mm_SMD +Package_BGA:Lattice_caBGA-756_27x27mm_Layout32x32_P0.8mm +Package_BGA:Lattice_iCE40_csBGA-132_8x8mm_Layout14x14_P0.5mm Package_BGA:LFBGA-100_10x10mm_Layout10x10_P0.8mm Package_BGA:LFBGA-144_10x10mm_Layout12x12_P0.8mm -Package_BGA:LFBGA-169_16x12mm_Layout28x14_P0.5mm_Ball0.3_Pad0.3mm_NSMD +Package_BGA:LFBGA-153_11.5x13mm_Layout14x14_P0.5mm +Package_BGA:LFBGA-169_12x16mm_Layout14x28_P0.5mm +Package_BGA:LFBGA-169_12x18mm_Layout14x28_P0.5mm +Package_BGA:LFBGA-169_14x18mm_Layout14x28_P0.5mm +Package_BGA:LFBGA-289_14x14mm_Layout17x17_P0.8mm Package_BGA:LFBGA-400_16x16mm_Layout20x20_P0.8mm Package_BGA:LFBGA-484_18x18mm_Layout22x22_P0.8mm Package_BGA:Linear_BGA-133_15.0x15.0mm_Layout12x12_P1.27mm -Package_BGA:MAPBGA_14x14mm_Layout17x17_P0.8mm -Package_BGA:MAPBGA_9x9mm_Layout17x17_P0.5mm +Package_BGA:MAPBGA-272_9x9mm_Layout17x17_P0.5mm +Package_BGA:MAPBGA-289_14x14mm_Layout17x17_P0.8mm Package_BGA:Maxim_WLP-12 -Package_BGA:Maxim_WLP-12_1.608x2.008mm_Layout4x3_P0.4mm_Ball0.27mm_Pad0.25mm_NSMD +Package_BGA:Maxim_WLP-12_2.008x1.608mm_Layout4x3_P0.4mm Package_BGA:Maxim_WLP-9_1.595x1.415_Layout3x3_P0.4mm_Ball0.27mm_Pad0.25mm_NSMD Package_BGA:Microchip_TFBGA-196_11x11mm_Layout14x14_P0.75mm_SMD Package_BGA:Micron_FBGA-78_7.5x10.6mm_Layout9x13_P0.8mm @@ -9570,26 +10049,32 @@ Package_BGA:Micron_FBGA-78_9x10.5mm_Layout9x13_P0.8mm Package_BGA:Micron_FBGA-96_7.5x13.5mm_Layout9x16_P0.8mm Package_BGA:Micron_FBGA-96_8x14mm_Layout9x16_P0.8mm Package_BGA:Micron_FBGA-96_9x14mm_Layout9x16_P0.8mm -Package_BGA:NXP_VFBGA-42_2.6x3mm_Layout6x7_P0.4mm_Ball0.25mm_Pad0.24mm +Package_BGA:NXP_VFBGA-42_2.6x3mm_Layout6x7_P0.4mm Package_BGA:ST_LFBGA-354_16x16mm_Layout19x19_P0.8mm Package_BGA:ST_LFBGA-448_18x18mm_Layout22x22_P0.8mm +Package_BGA:ST_TFBGA-169_7x7mm_Layout13x13_P0.5mm Package_BGA:ST_TFBGA-225_13x13mm_Layout15x15_P0.8mm Package_BGA:ST_TFBGA-257_10x10mm_Layout19x19_P0.5mmP0.65mm +Package_BGA:ST_TFBGA-320_11x11mm_Layout21x21_P0.5mm Package_BGA:ST_TFBGA-361_12x12mm_Layout23x23_P0.5mmP0.65mm Package_BGA:ST_UFBGA-121_6x6mm_Layout11x11_P0.5mm Package_BGA:ST_UFBGA-129_7x7mm_Layout13x13_P0.5mm +Package_BGA:ST_UFBGA-59_5x5mm_Layout8x8_P0.5mm Package_BGA:ST_UFBGA-73_5x5mm_Layout9x9_P0.5mm +Package_BGA:ST_UFBGA-81_5x5mm_Layout9x9_P0.5mm Package_BGA:ST_uTFBGA-36_3.6x3.6mm_Layout6x6_P0.5mm -Package_BGA:Texas_BGA-289_15.0x15.0mm_Layout17x17_P0.8mm_Ball0.5mm_Pad0.4mm +Package_BGA:Texas_BGA-289_15x15mm_Layout17x17_P0.8mm Package_BGA:Texas_DSBGA-10_1.36x1.86mm_Layout3x4_P0.5mm Package_BGA:Texas_DSBGA-12_1.36x1.86mm_Layout3x4_P0.5mm Package_BGA:Texas_DSBGA-16_2.39x2.39mm_Layout4x4_P0.5mm -Package_BGA:Texas_DSBGA-28_1.9x3.0mm_Layout4x7_P0.4mm +Package_BGA:Texas_DSBGA-28_1.9x3mm_Layout4x7_P0.4mm Package_BGA:Texas_DSBGA-49_3.33x3.488mm_Layout7x7_P0.4mm Package_BGA:Texas_DSBGA-5_0.822x1.116mm_Layout2x1x2_P0.4mm Package_BGA:Texas_DSBGA-5_0.8875x1.3875mm_Layout2x3_P0.5mm +Package_BGA:Texas_DSBGA-5_1.5855x1.6365mm_Layout3x2_P0.5mm Package_BGA:Texas_DSBGA-64_3.415x3.535mm_Layout8x8_P0.4mm Package_BGA:Texas_DSBGA-6_0.704x1.054mm_Layout2x3_P0.35mm +Package_BGA:Texas_DSBGA-6_0.757x1.01mm_Layout2x3_P0.35mm Package_BGA:Texas_DSBGA-6_0.855x1.255mm_Layout2x3_P0.4mm_LevelB Package_BGA:Texas_DSBGA-6_0.855x1.255mm_Layout2x3_P0.4mm_LevelC Package_BGA:Texas_DSBGA-6_0.95x1.488mm_Layout2x3_P0.4mm @@ -9599,18 +10084,30 @@ Package_BGA:Texas_DSBGA-8_0.9x1.9mm_Layout2x4_P0.5mm Package_BGA:Texas_DSBGA-8_1.43x1.41mm_Layout3x3_P0.5mm Package_BGA:Texas_DSBGA-8_1.5195x1.5195mm_Layout3x3_P0.5mm Package_BGA:Texas_DSBGA-9_1.4715x1.4715mm_Layout3x3_P0.5mm -Package_BGA:Texas_MicroStar_Junior_BGA-113_7.0x7.0mm_Layout12x12_P0.5mm +Package_BGA:Texas_DSBGA-9_1.62x1.58mm_Layout3x3_P0.5mm +Package_BGA:Texas_MicroStar_Junior_BGA-113_7x7mm_Layout12x12_P0.5mm Package_BGA:Texas_MicroStar_Junior_BGA-12_2.0x2.5mm_Layout4x3_P0.5mm Package_BGA:Texas_MicroStar_Junior_BGA-80_5.0x5.0mm_Layout9x9_P0.5mm +Package_BGA:Texas_PicoStar_BGA-4_0.758x0.758mm_Layout2x2_P0.4mm +Package_BGA:Texas_YFP0020_DSBGA-20_1.588x1.988mm_Layout4x5_P0.4mm Package_BGA:TFBGA-100_5.5x5.5mm_Layout10x10_P0.5mm Package_BGA:TFBGA-100_8x8mm_Layout10x10_P0.8mm Package_BGA:TFBGA-100_9.0x9.0mm_Layout10x10_P0.8mm Package_BGA:TFBGA-121_10x10mm_Layout11x11_P0.8mm +Package_BGA:TFBGA-169_9x9mm_Layout13x13_P0.65mm Package_BGA:TFBGA-216_13x13mm_Layout15x15_P0.8mm Package_BGA:TFBGA-225_10x10mm_Layout15x15_P0.65mm +Package_BGA:TFBGA-256_13x13mm_Layout16x16_P0.8mm Package_BGA:TFBGA-265_14x14mm_Layout17x17_P0.8mm +Package_BGA:TFBGA-289_9x9mm_Layout17x17_P0.5mm +Package_BGA:TFBGA-324_12x12mm_Layout18x18_P0.65mm Package_BGA:TFBGA-361_13x13mm_Layout19x19_P0.65mm +Package_BGA:TFBGA-48_6x10mm_Layout6x8_P0.75mm +Package_BGA:TFBGA-49_3x3mm_Layout7x7_P0.4mm +Package_BGA:TFBGA-576_16x16mm_Layout24x24_P0.65mm +Package_BGA:TFBGA-644_19x19mm_Layout28x28_P0.65mm Package_BGA:TFBGA-64_5x5mm_Layout8x8_P0.5mm +Package_BGA:TFBGA-81_5x5mm_Layout9x9_P0.5mm Package_BGA:UCBGA-36_2.5x2.5mm_Layout6x6_P0.4mm Package_BGA:UCBGA-49_3x3mm_Layout7x7_P0.4mm Package_BGA:UCBGA-81_4x4mm_Layout9x9_P0.4mm @@ -9626,11 +10123,11 @@ Package_BGA:UFBGA-32_4.0x4.0mm_Layout6x6_P0.5mm Package_BGA:UFBGA-64_5x5mm_Layout8x8_P0.5mm Package_BGA:VFBGA-100_7.0x7.0mm_Layout10x10_P0.65mm Package_BGA:VFBGA-49_5.0x5.0mm_Layout7x7_P0.65mm -Package_BGA:VFBGA-86_6x6mm_Layout10x10_P0.55mm_Ball0.25mm_Pad0.2mm -Package_BGA:WLP-4_0.73x0.73mm_Layout2x2_P0.35mm_Ball0.22mm_Pad0.2mm_NSMD +Package_BGA:VFBGA-86_6x6mm_Layout10x10_P0.55mm +Package_BGA:WLP-4_0.728x0.728mm_Layout2x2_P0.35mm Package_BGA:WLP-4_0.83x0.83mm_P0.4mm Package_BGA:WLP-4_0.86x0.86mm_P0.4mm -Package_BGA:WLP-9_1.448x1.468mm_Layout3x3_P0.4mm_Ball0.27mm_Pad0.25mm +Package_BGA:WLP-9_1.468x1.448mm_Layout3x3_P0.4mm Package_BGA:XBGA-121_10x10mm_Layout11x11_P0.8mm Package_BGA:XFBGA-121_8x8mm_Layout11x11_P0.65mm Package_BGA:XFBGA-36_3.5x3.5mm_Layout6x6_P0.5mm @@ -9681,9 +10178,15 @@ Package_CSP:Analog_LFCSP-16-1EP_4x4mm_P0.65mm_EP2.35x2.35mm Package_CSP:Analog_LFCSP-16-1EP_4x4mm_P0.65mm_EP2.35x2.35mm_ThermalVias Package_CSP:Analog_LFCSP-8-1EP_3x3mm_P0.5mm_EP1.53x1.85mm Package_CSP:Analog_LFCSP-UQ-10_1.3x1.6mm_P0.4mm +Package_CSP:Anpec_WLCSP-20_1.76x2.03mm_Layout4x5_P0.4mm +Package_CSP:Dialog_WLCSP-34_4.54x1.66mm_Layout17x4_P0.25x0.34mm +Package_CSP:DiodesInc_GEA20_WLCSP-20_1.7x2.1mm_Layout4x5_P0.4mm Package_CSP:Efinix_WLCSP-64_3.5353x3.3753mm_Layout8x8_P0.4mm +Package_CSP:Efinix_WLCSP-80_4.4567x3.5569mm_Layout10x8_P0.4mm +Package_CSP:LFCSP-10_2x2mm_P0.5mm Package_CSP:LFCSP-16-1EP_3x3mm_P0.5mm_EP1.3x1.3mm Package_CSP:LFCSP-16-1EP_3x3mm_P0.5mm_EP1.3x1.3mm_ThermalVias +Package_CSP:LFCSP-16-1EP_3x3mm_P0.5mm_EP1.5x1.5mm Package_CSP:LFCSP-16-1EP_3x3mm_P0.5mm_EP1.6x1.6mm Package_CSP:LFCSP-16-1EP_3x3mm_P0.5mm_EP1.6x1.6mm_ThermalVias Package_CSP:LFCSP-16-1EP_3x3mm_P0.5mm_EP1.7x1.7mm @@ -9696,10 +10199,12 @@ Package_CSP:LFCSP-16-1EP_4x4mm_P0.65mm_EP2.6x2.6mm Package_CSP:LFCSP-16-1EP_4x4mm_P0.65mm_EP2.6x2.6mm_ThermalVias Package_CSP:LFCSP-16_3x3mm_P0.5mm Package_CSP:LFCSP-20-1EP_4x4mm_P0.5mm_EP2.1x2.1mm +Package_CSP:LFCSP-20-1EP_4x4mm_P0.5mm_EP2.1x2.1mm_ThermalVias Package_CSP:LFCSP-20-1EP_4x4mm_P0.5mm_EP2.5x2.5mm Package_CSP:LFCSP-20-1EP_4x4mm_P0.5mm_EP2.5x2.5mm_ThermalVias Package_CSP:LFCSP-20-1EP_4x4mm_P0.5mm_EP2.6x2.6mm Package_CSP:LFCSP-20-1EP_4x4mm_P0.5mm_EP2.6x2.6mm_ThermalVias +Package_CSP:LFCSP-24-1EP_4x4mm_P0.5mm_EP0.5x0.5mm Package_CSP:LFCSP-24-1EP_4x4mm_P0.5mm_EP2.3x2.3mm Package_CSP:LFCSP-24-1EP_4x4mm_P0.5mm_EP2.3x2.3mm_ThermalVias Package_CSP:LFCSP-24-1EP_4x4mm_P0.5mm_EP2.5x2.5mm @@ -9721,6 +10226,8 @@ Package_CSP:LFCSP-40-1EP_6x6mm_P0.5mm_EP4.6x4.6mm Package_CSP:LFCSP-40-1EP_6x6mm_P0.5mm_EP4.6x4.6mm_ThermalVias Package_CSP:LFCSP-48-1EP_7x7mm_P0.5mm_EP4.1x4.1mm Package_CSP:LFCSP-48-1EP_7x7mm_P0.5mm_EP4.1x4.1mm_ThermalVias +Package_CSP:LFCSP-56-1EP_8x8mm_P0.5mm_EP6.6x6.6mm +Package_CSP:LFCSP-56-1EP_8x8mm_P0.5mm_EP6.6x6.6mm_ThermalVias Package_CSP:LFCSP-6-1EP_2x2mm_P0.65mm_EP1x1.6mm Package_CSP:LFCSP-64-1EP_9x9mm_P0.5mm_EP5.21x5.21mm Package_CSP:LFCSP-64-1EP_9x9mm_P0.5mm_EP5.21x5.21mm_ThermalVias @@ -9738,39 +10245,55 @@ Package_CSP:LFCSP-WD-10-1EP_3x3mm_P0.5mm_EP1.64x2.38mm Package_CSP:LFCSP-WD-10-1EP_3x3mm_P0.5mm_EP1.64x2.38mm_ThermalVias Package_CSP:LFCSP-WD-8-1EP_3x3mm_P0.65mm_EP1.6x2.44mm Package_CSP:LFCSP-WD-8-1EP_3x3mm_P0.65mm_EP1.6x2.44mm_ThermalVias -Package_CSP:Maxim_WLCSP-35_3.0x2.17mm_Layout7x5_P0.4mm_Ball0.27mm_Pad0.25mm -Package_CSP:Nexperia_WLCSP-15_6-3-6_2.37x1.17mm_Layout6x3_P0.4mm +Package_CSP:Macronix_WLCSP-12_2.02x2.09mm_Layout4x4_P0.5mm +Package_CSP:Maxim_WLCSP-35_2.998x2.168mm_Layout7x5_P0.4mm +Package_CSP:Nexperia_WLCSP-15_2.37x1.17mm_Layout6x3_P0.4mmP0.8mm +Package_CSP:OnSemi_ODCSP36_BGA-36_6.13x6.13mm_Layout6x6_P1.0mm +Package_CSP:OnSemi_ODCSP36_BGA-36_6.13x6.13mm_Layout6x6_P1.0mm_ManualAssembly +Package_CSP:OnSemi_ODCSP8_BGA-8_3.16x3.16mm_Layout3x3_P1.26mm +Package_CSP:OnSemi_ODCSP8_BGA-8_3.16x3.16mm_Layout3x3_P1.26mm_ManualAssembly Package_CSP:pSemi_CSP-16_1.64x2.04mm_P0.4mm Package_CSP:pSemi_CSP-16_1.64x2.04mm_P0.4mm_Pad0.18mm -Package_CSP:ST_WLCSP-100_4.40x4.38mm_Layout10x10_P0.4mm_Offcenter Package_CSP:ST_WLCSP-100_4.437x4.456mm_Layout10x10_P0.4mm +Package_CSP:ST_WLCSP-100_4.4x4.38mm_Layout10x10_P0.4mm_Offcenter Package_CSP:ST_WLCSP-100_Die422 Package_CSP:ST_WLCSP-100_Die446 Package_CSP:ST_WLCSP-100_Die452 Package_CSP:ST_WLCSP-100_Die461 +Package_CSP:ST_WLCSP-101_3.86x3.79mm_Layout11x19_P0.35mm_Stagger Package_CSP:ST_WLCSP-104_Die437 -Package_CSP:ST_WLCSP-115_3.73x4.15mm_P0.35mm_Stagger -Package_CSP:ST_WLCSP-115_4.63x4.15mm_P0.4mm_Stagger +Package_CSP:ST_WLCSP-115_3.73x4.15mm_Layout11x21_P0.35mm_Stagger +Package_CSP:ST_WLCSP-115_4.63x4.15mm_Layout21x11_P0.4mm_Stagger +Package_CSP:ST_WLCSP-12_1.7x1.42mm_Layout4x6_P0.35mm_Stagger Package_CSP:ST_WLCSP-132_4.57x4.37mm_Layout12x11_P0.35mm Package_CSP:ST_WLCSP-143_Die419 Package_CSP:ST_WLCSP-143_Die449 Package_CSP:ST_WLCSP-144_Die470 +Package_CSP:ST_WLCSP-150_5.38x5.47mm_Layout13x23_P0.4mm_Stagger Package_CSP:ST_WLCSP-156_4.96x4.64mm_Layout13x12_P0.35mm Package_CSP:ST_WLCSP-168_Die434 Package_CSP:ST_WLCSP-180_Die451 -Package_CSP:ST_WLCSP-18_1.86x2.14mm_P0.4mm_Stagger -Package_CSP:ST_WLCSP-20_1.94x2.40mm_Layout4x5_P0.4mm -Package_CSP:ST_WLCSP-25_2.30x2.48mm_Layout5x5_P0.4mm +Package_CSP:ST_WLCSP-18_1.86x2.14mm_Layout7x5_P0.4mm_Stagger +Package_CSP:ST_WLCSP-208_5.38x5.47mm_Layout26x16_P0.35mm_Stagger +Package_CSP:ST_WLCSP-208_5.8x5.6mm_Layout26x16_P0.35mm_Stagger +Package_CSP:ST_WLCSP-20_1.94x2.4mm_Layout4x5_P0.4mm +Package_CSP:ST_WLCSP-25_2.33x2.24mm_Layout5x5_P0.4mm +Package_CSP:ST_WLCSP-25_2.3x2.48mm_Layout5x5_P0.4mm Package_CSP:ST_WLCSP-25_Die425 Package_CSP:ST_WLCSP-25_Die444 Package_CSP:ST_WLCSP-25_Die457 +Package_CSP:ST_WLCSP-27_2.34x2.55mm_Layout9x6_P0.4mm_Stagger +Package_CSP:ST_WLCSP-27_2.55x2.34mm_P0.40mm_Stagger Package_CSP:ST_WLCSP-36_2.58x3.07mm_Layout6x6_P0.4mm Package_CSP:ST_WLCSP-36_Die417 Package_CSP:ST_WLCSP-36_Die440 Package_CSP:ST_WLCSP-36_Die445 Package_CSP:ST_WLCSP-36_Die458 +Package_CSP:ST_WLCSP-41_2.98x2.76mm_Layout13x7_P0.4mm_Stagger +Package_CSP:ST_WLCSP-42_2.82x2.93mm_P0.40mm_Stagger +Package_CSP:ST_WLCSP-42_2.93x2.82mm_Layout12x7_P0.4mm_Stagger Package_CSP:ST_WLCSP-49_3.15x3.13mm_Layout7x7_P0.4mm -Package_CSP:ST_WLCSP-49_3.30x3.38mm_Layout7x7_P0.4mm_Offcenter +Package_CSP:ST_WLCSP-49_3.3x3.38mm_Layout7x7_P0.4mm_Offcenter Package_CSP:ST_WLCSP-49_Die423 Package_CSP:ST_WLCSP-49_Die431 Package_CSP:ST_WLCSP-49_Die433 @@ -9779,7 +10302,8 @@ Package_CSP:ST_WLCSP-49_Die438 Package_CSP:ST_WLCSP-49_Die439 Package_CSP:ST_WLCSP-49_Die447 Package_CSP:ST_WLCSP-49_Die448 -Package_CSP:ST_WLCSP-52_3.09x3.15mm_P0.4mm_Stagger +Package_CSP:ST_WLCSP-52_3.09x3.15mm_Layout13x8_P0.4mm_Stagger +Package_CSP:ST_WLCSP-56_3.38x3.38mm_Layout14x8_P0.4mm_Stagger Package_CSP:ST_WLCSP-63_Die427 Package_CSP:ST_WLCSP-64_3.56x3.52mm_Layout8x8_P0.4mm Package_CSP:ST_WLCSP-64_Die414 @@ -9791,33 +10315,45 @@ Package_CSP:ST_WLCSP-64_Die442 Package_CSP:ST_WLCSP-64_Die462 Package_CSP:ST_WLCSP-66_Die411 Package_CSP:ST_WLCSP-66_Die432 +Package_CSP:ST_WLCSP-72_3.38x3.38mm_Layout16x9_P0.35mm_Stagger Package_CSP:ST_WLCSP-72_Die415 +Package_CSP:ST_WLCSP-80_3.5x3.27mm_Layout10x16_P0.35mm_Stagger_Offcenter Package_CSP:ST_WLCSP-81_4.02x4.27mm_Layout9x9_P0.4mm Package_CSP:ST_WLCSP-81_4.36x4.07mm_Layout9x9_P0.4mm Package_CSP:ST_WLCSP-81_Die415 Package_CSP:ST_WLCSP-81_Die421 Package_CSP:ST_WLCSP-81_Die463 -Package_CSP:ST_WLCSP-90_4.20x3.95mm_P0.4mm_Stagger +Package_CSP:ST_WLCSP-90_4.2x3.95mm_Layout18x10_P0.4mm_Stagger Package_CSP:ST_WLCSP-90_Die413 -Package_CSP:WLCSP-12_1.403x1.555mm_P0.4mm_Stagger +Package_CSP:ST_WLCSP-99_4.42x3.77mm_Layout11x9_P0.35mm +Package_CSP:WLCSP-12_1.403x1.555mm_Layout6x4_P0.4mm_Stagger Package_CSP:WLCSP-12_1.56x1.56mm_P0.4mm -Package_CSP:WLCSP-16_1.409x1.409mm_P0.35mm -Package_CSP:WLCSP-16_2.225x2.17mm_P0.5mm +Package_CSP:WLCSP-16_1.409x1.409mm_Layout4x4_P0.35mm +Package_CSP:WLCSP-16_2.225x2.17mm_Layout4x4_P0.5mm Package_CSP:WLCSP-16_4x4_B2.17x2.32mm_P0.5mm Package_CSP:WLCSP-20_1.934x2.434mm_Layout4x5_P0.4mm Package_CSP:WLCSP-20_1.994x1.609mm_Layout5x4_P0.4mm Package_CSP:WLCSP-20_1.994x1.94mm_Layout4x5_P0.4mm Package_CSP:WLCSP-36_2.374x2.459mm_Layout6x6_P0.35mm Package_CSP:WLCSP-36_2.82x2.67mm_Layout6x6_P0.4mm -Package_CSP:WLCSP-4-X1-WLB0909-4_0.89x0.89mm_P0.5mm -Package_CSP:WLCSP-4_0.64x0.64mm_P0.35mm +Package_CSP:WLCSP-4_0.64x0.64mm_Layout2x2_P0.35mm +Package_CSP:WLCSP-4_0.89x0.89mm_Layout2x2_P0.5mm Package_CSP:WLCSP-56_3.170x3.444mm_Layout7x8_P0.4mm Package_CSP:WLCSP-6_1.4x1.0mm_P0.4mm Package_CSP:WLCSP-81_4.41x3.76mm_P0.4mm -Package_CSP:WLCSP-8_1.551x2.284mm_P0.5mm +Package_CSP:WLCSP-8_1.551x2.284mm_Layout2x4_P0.5mm Package_CSP:WLCSP-8_1.58x1.63x0.35mm_Layout3x5_P0.35x0.4mm_Ball0.25mm_Pad0.25mm_NSMD +Package_CSP:WLCSP-9_1.21x1.22mm_Layout3x3_P0.4mm Package_DFN_QFN:AMS_QFN-4-1EP_2x2mm_P0.95mm_EP0.7x1.6mm +Package_DFN_QFN:Analog_QFN-28-36-2EP_5x6mm_P0.5mm +Package_DFN_QFN:AO_AOZ666xDI_DFN-8-1EP_3x3mm_P0.65mm_EP1.25x2.7mm Package_DFN_QFN:AO_DFN-8-1EP_5.55x5.2mm_P1.27mm_EP4.12x4.6mm +Package_DFN_QFN:ArtInChip_QFN-100-1EP_12x12mm_P0.4mm_EP7.4x7.4mm +Package_DFN_QFN:ArtInChip_QFN-100-1EP_12x12mm_P0.4mm_EP7.4x7.4mm_ThermalVias +Package_DFN_QFN:ArtInChip_QFN-68-1EP_7x7mm_P0.35mm_EP5.49x5.49mm +Package_DFN_QFN:ArtInChip_QFN-68-1EP_7x7mm_P0.35mm_EP5.49x5.49mm_ThermalVias +Package_DFN_QFN:ArtInChip_QFN-88-1EP_10x10mm_P0.4mm_EP6.74x6.74mm +Package_DFN_QFN:ArtInChip_QFN-88-1EP_10x10mm_P0.4mm_EP6.74x6.74mm_ThermalVias Package_DFN_QFN:Cypress_QFN-56-1EP_8x8mm_P0.5mm_EP6.22x6.22mm_ThermalVias Package_DFN_QFN:DFN-10-1EP_2.6x2.6mm_P0.5mm_EP1.3x2.2mm Package_DFN_QFN:DFN-10-1EP_2.6x2.6mm_P0.5mm_EP1.3x2.2mm_ThermalVias @@ -9831,8 +10367,8 @@ Package_DFN_QFN:DFN-10-1EP_3x3mm_P0.5mm_EP1.75x2.7mm Package_DFN_QFN:DFN-10-1EP_3x3mm_P0.5mm_EP1.7x2.5mm Package_DFN_QFN:DFN-10_2x2mm_P0.4mm Package_DFN_QFN:DFN-12-1EP_2x3mm_P0.45mm_EP0.64x2.4mm -Package_DFN_QFN:DFN-12-1EP_3x3mm_P0.45mm_EP1.66x2.38mm -Package_DFN_QFN:DFN-12-1EP_3x3mm_P0.5mm_EP1.4x2.55mm +Package_DFN_QFN:DFN-12-1EP_3x3mm_P0.45mm_EP1.65x2.38mm +Package_DFN_QFN:DFN-12-1EP_3x3mm_P0.45mm_EP1.65x2.38mm_ThermalVias Package_DFN_QFN:DFN-12-1EP_3x3mm_P0.5mm_EP1.6x2.5mm Package_DFN_QFN:DFN-12-1EP_3x3mm_P0.5mm_EP1.6x2.5mm_ThermalVias Package_DFN_QFN:DFN-12-1EP_3x3mm_P0.5mm_EP2.05x2.86mm @@ -9843,7 +10379,6 @@ Package_DFN_QFN:DFN-14-1EP_3x3mm_P0.4mm_EP1.78x2.35mm Package_DFN_QFN:DFN-14-1EP_3x4.5mm_P0.65mm_EP1.65x4.25mm Package_DFN_QFN:DFN-14-1EP_3x4.5mm_P0.65mm_EP1.65x4.25mm_ThermalVias Package_DFN_QFN:DFN-14-1EP_3x4mm_P0.5mm_EP1.7x3.3mm -Package_DFN_QFN:DFN-14-1EP_4x4mm_P0.5mm_EP2.86x3.6mm Package_DFN_QFN:DFN-14_1.35x3.5mm_P0.5mm Package_DFN_QFN:DFN-16-1EP_3x4mm_P0.45mm_EP1.7x3.3mm Package_DFN_QFN:DFN-16-1EP_3x5mm_P0.5mm_EP1.66x4.4mm @@ -9856,36 +10391,44 @@ Package_DFN_QFN:DFN-22-1EP_5x6mm_P0.5mm_EP3.14x4.3mm Package_DFN_QFN:DFN-24-1EP_4x7mm_P0.5mm_EP2.64x6.44mm Package_DFN_QFN:DFN-32-1EP_4x7mm_P0.4mm_EP2.64x6.44mm Package_DFN_QFN:DFN-44-1EP_5x8.9mm_P0.4mm_EP3.7x8.4mm +Package_DFN_QFN:DFN-4_5x7mm_P5.08mm Package_DFN_QFN:DFN-6-1EP_1.2x1.2mm_P0.4mm_EP0.3x0.94mm_PullBack Package_DFN_QFN:DFN-6-1EP_2x1.6mm_P0.5mm_EP1.15x1.3mm Package_DFN_QFN:DFN-6-1EP_2x1.8mm_P0.5mm_EP1.2x1.6mm Package_DFN_QFN:DFN-6-1EP_2x2mm_P0.5mm_EP0.61x1.42mm Package_DFN_QFN:DFN-6-1EP_2x2mm_P0.5mm_EP0.6x1.37mm +Package_DFN_QFN:DFN-6-1EP_2x2mm_P0.5mm_EP0.7x1.6mm Package_DFN_QFN:DFN-6-1EP_2x2mm_P0.65mm_EP1.01x1.7mm Package_DFN_QFN:DFN-6-1EP_2x2mm_P0.65mm_EP1x1.6mm Package_DFN_QFN:DFN-6-1EP_3x2mm_P0.5mm_EP1.65x1.35mm Package_DFN_QFN:DFN-6-1EP_3x3mm_P0.95mm_EP1.7x2.6mm Package_DFN_QFN:DFN-6-1EP_3x3mm_P1mm_EP1.5x2.4mm Package_DFN_QFN:DFN-6_1.3x1.2mm_P0.4mm -Package_DFN_QFN:DFN-8-1EP_2x2mm_P0.45mm_EP0.64x1.38mm +Package_DFN_QFN:DFN-6_1.6x1.3mm_P0.4mm +Package_DFN_QFN:DFN-8-1EP_1.5x1.5mm_P0.4mm_EP0.7x1.2mm +Package_DFN_QFN:DFN-8-1EP_2x2mm_P0.45mm_EP0.64x1.37mm Package_DFN_QFN:DFN-8-1EP_2x2mm_P0.5mm_EP0.6x1.2mm -Package_DFN_QFN:DFN-8-1EP_2x2mm_P0.5mm_EP0.7x1.3mm +Package_DFN_QFN:DFN-8-1EP_2x2mm_P0.5mm_EP0.86x1.55mm Package_DFN_QFN:DFN-8-1EP_2x2mm_P0.5mm_EP0.8x1.6mm Package_DFN_QFN:DFN-8-1EP_2x2mm_P0.5mm_EP0.9x1.3mm Package_DFN_QFN:DFN-8-1EP_2x2mm_P0.5mm_EP0.9x1.5mm Package_DFN_QFN:DFN-8-1EP_2x2mm_P0.5mm_EP0.9x1.6mm +Package_DFN_QFN:DFN-8-1EP_2x2mm_P0.5mm_EP0.9x1.7mm Package_DFN_QFN:DFN-8-1EP_2x2mm_P0.5mm_EP1.05x1.75mm -Package_DFN_QFN:DFN-8-1EP_2x3mm_P0.5mm_EP0.56x2.15mm Package_DFN_QFN:DFN-8-1EP_2x3mm_P0.5mm_EP0.61x2.2mm Package_DFN_QFN:DFN-8-1EP_3x2mm_P0.45mm_EP1.66x1.36mm Package_DFN_QFN:DFN-8-1EP_3x2mm_P0.5mm_EP1.36x1.46mm Package_DFN_QFN:DFN-8-1EP_3x2mm_P0.5mm_EP1.3x1.5mm Package_DFN_QFN:DFN-8-1EP_3x2mm_P0.5mm_EP1.75x1.45mm Package_DFN_QFN:DFN-8-1EP_3x2mm_P0.5mm_EP1.7x1.4mm +Package_DFN_QFN:DFN-8-1EP_3x2mm_P0.5mm_EP1.7x1.6mm Package_DFN_QFN:DFN-8-1EP_3x3mm_P0.5mm_EP1.65x2.38mm Package_DFN_QFN:DFN-8-1EP_3x3mm_P0.5mm_EP1.65x2.38mm_ThermalVias Package_DFN_QFN:DFN-8-1EP_3x3mm_P0.5mm_EP1.66x2.38mm +Package_DFN_QFN:DFN-8-1EP_3x3mm_P0.5mm_EP1.7x2.4mm +Package_DFN_QFN:DFN-8-1EP_3x3mm_P0.5mm_EP1.7x2.4mm_ThermalVias Package_DFN_QFN:DFN-8-1EP_3x3mm_P0.65mm_EP1.55x2.4mm +Package_DFN_QFN:DFN-8-1EP_3x3mm_P0.65mm_EP1.5x2.25mm Package_DFN_QFN:DFN-8-1EP_3x3mm_P0.65mm_EP1.7x2.05mm Package_DFN_QFN:DFN-8-1EP_4x4mm_P0.8mm_EP2.39x2.21mm Package_DFN_QFN:DFN-8-1EP_4x4mm_P0.8mm_EP2.3x3.24mm @@ -9900,6 +10443,9 @@ Package_DFN_QFN:DHVQFN-20-1EP_2.5x4.5mm_P0.5mm_EP1x3mm Package_DFN_QFN:Diodes_DFN1006-3 Package_DFN_QFN:Diodes_UDFN-10_1.0x2.5mm_P0.5mm Package_DFN_QFN:Diodes_UDFN2020-6_Type-F +Package_DFN_QFN:Diodes_UDFN3810-9_TYPE_B +Package_DFN_QFN:Diodes_ZL32_TQFN-32-1EP_3x6mm_P0.4mm_EP1.25x3.5mm +Package_DFN_QFN:EPC_QFN-13-3EP_3.5x5mm_P0.5mm Package_DFN_QFN:HVQFN-16-1EP_3x3mm_P0.5mm_EP1.5x1.5mm Package_DFN_QFN:HVQFN-24-1EP_4x4mm_P0.5mm_EP2.1x2.1mm Package_DFN_QFN:HVQFN-24-1EP_4x4mm_P0.5mm_EP2.5x2.5mm @@ -9910,6 +10456,8 @@ Package_DFN_QFN:HVQFN-32-1EP_5x5mm_P0.5mm_EP3.1x3.1mm Package_DFN_QFN:HVQFN-32-1EP_5x5mm_P0.5mm_EP3.1x3.1mm_ThermalVias Package_DFN_QFN:HVQFN-40-1EP_6x6mm_P0.5mm_EP4.1x4.1mm Package_DFN_QFN:HVQFN-40-1EP_6x6mm_P0.5mm_EP4.1x4.1mm_ThermalVias +Package_DFN_QFN:HXQFN-16-1EP_3x3mm_P0.5mm_EP1.85x1.85mm +Package_DFN_QFN:HXQFN-16-1EP_3x3mm_P0.5mm_EP1.85x1.85mm_ThermalVias Package_DFN_QFN:Infineon_MLPQ-16-14-1EP_4x4mm_P0.5mm Package_DFN_QFN:Infineon_MLPQ-40-32-1EP_7x7mm_P0.5mm Package_DFN_QFN:Infineon_MLPQ-48-1EP_7x7mm_P0.5mm_EP5.15x5.15mm @@ -9921,6 +10469,9 @@ Package_DFN_QFN:Linear_UGK52_QFN-46-52 Package_DFN_QFN:LQFN-10-1EP_2x2mm_P0.5mm_EP0.7x0.7mm Package_DFN_QFN:LQFN-12-1EP_2x2mm_P0.5mm_EP0.7x0.7mm Package_DFN_QFN:LQFN-16-1EP_3x3mm_P0.5mm_EP1.7x1.7mm +Package_DFN_QFN:Maxim_FC2QFN-14_2.5x2.5mm_P0.5mm +Package_DFN_QFN:Maxim_TDFN-12-1EP_3x3mm_P0.5mm_EP1.7x2.5mm +Package_DFN_QFN:Maxim_TDFN-12-1EP_3x3mm_P0.5mm_EP1.7x2.5mm_ThermalVias Package_DFN_QFN:Maxim_TDFN-6-1EP_3x3mm_P0.95mm_EP1.5x2.3mm Package_DFN_QFN:Micrel_MLF-8-1EP_2x2mm_P0.5mm_EP0.6x1.2mm Package_DFN_QFN:Micrel_MLF-8-1EP_2x2mm_P0.5mm_EP0.6x1.2mm_ThermalVias @@ -9939,12 +10490,15 @@ Package_DFN_QFN:MLF-6-1EP_1.6x1.6mm_P0.5mm_EP0.5x1.26mm Package_DFN_QFN:MLF-8-1EP_3x3mm_P0.65mm_EP1.55x2.3mm Package_DFN_QFN:MLF-8-1EP_3x3mm_P0.65mm_EP1.55x2.3mm_ThermalVias Package_DFN_QFN:MLPQ-16-1EP_4x4mm_P0.65mm_EP2.8x2.8mm +Package_DFN_QFN:MPS_QFN-12_2x2mm_P0.4mm Package_DFN_QFN:Nordic_AQFN-73-1EP_7x7mm_P0.5mm Package_DFN_QFN:Nordic_AQFN-94-1EP_7x7mm_P0.4mm Package_DFN_QFN:NXP_LQFN-48-1EP_7x7mm_P0.5mm_EP3.5x3.5mm_16xMask0.45x0.45 Package_DFN_QFN:NXP_LQFN-48-1EP_7x7mm_P0.5mm_EP3.5x3.5mm_16xMask0.45x0.45_ThermalVias +Package_DFN_QFN:OnSemi_DFN-14-1EP_4x4mm_P0.5mm_EP2.7x3.4mm Package_DFN_QFN:OnSemi_DFN-8_2x2mm_P0.5mm Package_DFN_QFN:OnSemi_SIP-38-6EP-9x7mm_P0.65mm_EP1.2x1.2mm +Package_DFN_QFN:OnSemi_UDFN-16-1EP_1.35x3.3mm_P0.4mm_EP0.4x2.8mm Package_DFN_QFN:OnSemi_UDFN-8_1.2x1.8mm_P0.4mm Package_DFN_QFN:OnSemi_VCT-28_3.5x3.5mm_P0.4mm Package_DFN_QFN:OnSemi_XDFN-10_1.35x2.2mm_P0.4mm @@ -9957,8 +10511,11 @@ Package_DFN_QFN:QFN-12-1EP_3x3mm_P0.5mm_EP1.65x1.65mm Package_DFN_QFN:QFN-12-1EP_3x3mm_P0.5mm_EP1.65x1.65mm_ThermalVias Package_DFN_QFN:QFN-12-1EP_3x3mm_P0.5mm_EP1.6x1.6mm Package_DFN_QFN:QFN-12-1EP_3x3mm_P0.5mm_EP1.6x1.6mm_ThermalVias +Package_DFN_QFN:QFN-16-1EP_1.8x2.6mm_P0.4mm_EP0.7x1.5mm +Package_DFN_QFN:QFN-16-1EP_1.8x2.6mm_P0.4mm_EP0.7x1.5mm_ThermalVias Package_DFN_QFN:QFN-16-1EP_3x3mm_P0.5mm_EP1.45x1.45mm Package_DFN_QFN:QFN-16-1EP_3x3mm_P0.5mm_EP1.45x1.45mm_ThermalVias +Package_DFN_QFN:QFN-16-1EP_3x3mm_P0.5mm_EP1.675x1.675mm Package_DFN_QFN:QFN-16-1EP_3x3mm_P0.5mm_EP1.75x1.75mm Package_DFN_QFN:QFN-16-1EP_3x3mm_P0.5mm_EP1.75x1.75mm_ThermalVias Package_DFN_QFN:QFN-16-1EP_3x3mm_P0.5mm_EP1.7x1.7mm @@ -10003,6 +10560,7 @@ Package_DFN_QFN:QFN-24-1EP_3x4mm_P0.4mm_EP1.65x2.65mm Package_DFN_QFN:QFN-24-1EP_3x4mm_P0.4mm_EP1.65x2.65mm_ThermalVias Package_DFN_QFN:QFN-24-1EP_4x4mm_P0.5mm_EP2.15x2.15mm Package_DFN_QFN:QFN-24-1EP_4x4mm_P0.5mm_EP2.15x2.15mm_ThermalVias +Package_DFN_QFN:QFN-24-1EP_4x4mm_P0.5mm_EP2.5x2.5mm Package_DFN_QFN:QFN-24-1EP_4x4mm_P0.5mm_EP2.65x2.65mm Package_DFN_QFN:QFN-24-1EP_4x4mm_P0.5mm_EP2.65x2.65mm_ThermalVias Package_DFN_QFN:QFN-24-1EP_4x4mm_P0.5mm_EP2.6x2.6mm @@ -10029,14 +10587,22 @@ Package_DFN_QFN:QFN-28-1EP_3x6mm_P0.5mm_EP1.7x4.75mm Package_DFN_QFN:QFN-28-1EP_3x6mm_P0.5mm_EP1.7x4.75mm_ThermalVias Package_DFN_QFN:QFN-28-1EP_4x4mm_P0.45mm_EP2.4x2.4mm Package_DFN_QFN:QFN-28-1EP_4x4mm_P0.45mm_EP2.4x2.4mm_ThermalVias +Package_DFN_QFN:QFN-28-1EP_4x4mm_P0.45mm_EP2.6x2.6mm Package_DFN_QFN:QFN-28-1EP_4x4mm_P0.4mm_EP2.3x2.3mm Package_DFN_QFN:QFN-28-1EP_4x4mm_P0.4mm_EP2.3x2.3mm_ThermalVias Package_DFN_QFN:QFN-28-1EP_4x4mm_P0.4mm_EP2.4x2.4mm Package_DFN_QFN:QFN-28-1EP_4x4mm_P0.4mm_EP2.4x2.4mm_ThermalVias Package_DFN_QFN:QFN-28-1EP_4x4mm_P0.4mm_EP2.6x2.6mm Package_DFN_QFN:QFN-28-1EP_4x4mm_P0.4mm_EP2.6x2.6mm_ThermalVias +Package_DFN_QFN:QFN-28-1EP_4x4mm_P0.4mm_EP2.7x2.7mm Package_DFN_QFN:QFN-28-1EP_4x5mm_P0.5mm_EP2.65x3.65mm Package_DFN_QFN:QFN-28-1EP_4x5mm_P0.5mm_EP2.65x3.65mm_ThermalVias +Package_DFN_QFN:QFN-28-1EP_5x5mm_P0.5mm_EP2.7x2.7mm +Package_DFN_QFN:QFN-28-1EP_5x5mm_P0.5mm_EP2.7x2.7mm_ThermalVias +Package_DFN_QFN:QFN-28-1EP_5x5mm_P0.5mm_EP3.1x3.1mm +Package_DFN_QFN:QFN-28-1EP_5x5mm_P0.5mm_EP3.1x3.1mm_ThermalVias +Package_DFN_QFN:QFN-28-1EP_5x5mm_P0.5mm_EP3.25x3.25mm +Package_DFN_QFN:QFN-28-1EP_5x5mm_P0.5mm_EP3.25x3.25mm_ThermalVias Package_DFN_QFN:QFN-28-1EP_5x5mm_P0.5mm_EP3.35x3.35mm Package_DFN_QFN:QFN-28-1EP_5x5mm_P0.5mm_EP3.35x3.35mm_ThermalVias Package_DFN_QFN:QFN-28-1EP_5x5mm_P0.5mm_EP3.75x3.75mm @@ -10120,6 +10686,8 @@ Package_DFN_QFN:QFN-48-1EP_7x7mm_P0.5mm_EP5.45x5.45mm Package_DFN_QFN:QFN-48-1EP_7x7mm_P0.5mm_EP5.45x5.45mm_ThermalVias Package_DFN_QFN:QFN-48-1EP_7x7mm_P0.5mm_EP5.6x5.6mm Package_DFN_QFN:QFN-48-1EP_7x7mm_P0.5mm_EP5.6x5.6mm_ThermalVias +Package_DFN_QFN:QFN-48-1EP_7x7mm_P0.5mm_EP5.7x5.7mm +Package_DFN_QFN:QFN-48-1EP_7x7mm_P0.5mm_EP5.7x5.7mm_ThermalVias Package_DFN_QFN:QFN-48-1EP_8x8mm_P0.5mm_EP6.2x6.2mm Package_DFN_QFN:QFN-48-1EP_8x8mm_P0.5mm_EP6.2x6.2mm_ThermalVias Package_DFN_QFN:QFN-52-1EP_7x8mm_P0.5mm_EP5.41x6.45mm @@ -10141,6 +10709,8 @@ Package_DFN_QFN:QFN-56-1EP_8x8mm_P0.5mm_EP5.9x5.9mm Package_DFN_QFN:QFN-56-1EP_8x8mm_P0.5mm_EP5.9x5.9mm_ThermalVias Package_DFN_QFN:QFN-56-1EP_8x8mm_P0.5mm_EP6.1x6.1mm Package_DFN_QFN:QFN-56-1EP_8x8mm_P0.5mm_EP6.1x6.1mm_ThermalVias +Package_DFN_QFN:QFN-60-1EP_7x7mm_P0.4mm_EP3.4x3.4mm +Package_DFN_QFN:QFN-60-1EP_7x7mm_P0.4mm_EP3.4x3.4mm_ThermalVias Package_DFN_QFN:QFN-64-1EP_8x8mm_P0.4mm_EP6.5x6.5mm Package_DFN_QFN:QFN-64-1EP_8x8mm_P0.4mm_EP6.5x6.5mm_ThermalVias Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP3.4x3.4mm @@ -10159,6 +10729,8 @@ Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP5.45x5.45mm Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP5.45x5.45mm_ThermalVias Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP5.4x5.4mm Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP5.4x5.4mm_ThermalVias +Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP5.7x5.7mm +Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP5.7x5.7mm_ThermalVias Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP6x6mm Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP6x6mm_ThermalVias Package_DFN_QFN:QFN-64-1EP_9x9mm_P0.5mm_EP7.15x7.15mm @@ -10181,10 +10753,15 @@ Package_DFN_QFN:QFN-76-1EP_9x9mm_P0.4mm_EP3.8x3.8mm Package_DFN_QFN:QFN-76-1EP_9x9mm_P0.4mm_EP3.8x3.8mm_ThermalVias Package_DFN_QFN:QFN-76-1EP_9x9mm_P0.4mm_EP5.81x6.31mm Package_DFN_QFN:QFN-76-1EP_9x9mm_P0.4mm_EP5.81x6.31mm_ThermalVias +Package_DFN_QFN:QFN-80-1EP_10x10mm_P0.4mm_EP3.4x3.4mm +Package_DFN_QFN:QFN-80-1EP_10x10mm_P0.4mm_EP3.4x3.4mm_ThermalVias Package_DFN_QFN:Qorvo_DFN-8-1EP_2x2mm_P0.5mm +Package_DFN_QFN:Qorvo_TQFN66-48-1EP_6x6mm_P0.4mm_EP4.2x4.2mm +Package_DFN_QFN:Qorvo_TQFN66-48-1EP_6x6mm_P0.4mm_EP4.2x4.2mm_ThermalVias Package_DFN_QFN:ROHM_DFN0604-3 Package_DFN_QFN:SiliconLabs_QFN-20-1EP_3x3mm_P0.5mm_EP1.8x1.8mm Package_DFN_QFN:SiliconLabs_QFN-20-1EP_3x3mm_P0.5mm_EP1.8x1.8mm_ThermalVias +Package_DFN_QFN:ST_UFDFPN-12-1EP_3x3mm_P0.5mm_EP1.4x2.55mm Package_DFN_QFN:ST_UFQFPN-20_3x3mm_P0.5mm Package_DFN_QFN:ST_UQFN-6L_1.5x1.7mm_P0.5mm Package_DFN_QFN:TDFN-10-1EP_2x3mm_P0.5mm_EP0.9x2mm @@ -10206,71 +10783,86 @@ Package_DFN_QFN:Texas_B3QFN-14-1EP_5x5.5mm_P0.65mm Package_DFN_QFN:Texas_B3QFN-14-1EP_5x5.5mm_P0.65mm_ThermalVia Package_DFN_QFN:Texas_DRB0008A Package_DFN_QFN:Texas_MOF0009A +Package_DFN_QFN:Texas_PicoStar_DFN-3_0.69x0.60mm Package_DFN_QFN:Texas_QFN-41_10x16mm Package_DFN_QFN:Texas_R-PUQFN-N10 Package_DFN_QFN:Texas_R-PUQFN-N12 -Package_DFN_QFN:Texas_R-PWQFN-N28_EP2.1x3.1mm -Package_DFN_QFN:Texas_R-PWQFN-N28_EP2.1x3.1mm_ThermalVias -Package_DFN_QFN:Texas_RGE0024C_EP2.1x2.1mm -Package_DFN_QFN:Texas_RGE0024C_EP2.1x2.1mm_ThermalVias -Package_DFN_QFN:Texas_RGE0024H_EP2.7x2.7mm -Package_DFN_QFN:Texas_RGE0024H_EP2.7x2.7mm_ThermalVias -Package_DFN_QFN:Texas_RGV_S-PVQFN-N16_EP2.1x2.1mm -Package_DFN_QFN:Texas_RGV_S-PVQFN-N16_EP2.1x2.1mm_ThermalVias +Package_DFN_QFN:Texas_REF0038A_WQFN-38-2EP_6x4mm_P0.4 +Package_DFN_QFN:Texas_RGC0064B_VQFN-64-1EP_9x9mm_P0.5mm_EP4.25x4.25mm +Package_DFN_QFN:Texas_RGC0064B_VQFN-64-1EP_9x9mm_P0.5mm_EP4.25x4.25mm_ThermalVias +Package_DFN_QFN:Texas_RGE0024C_VQFN-24-1EP_4x4mm_P0.5mm_EP2.1x2.1mm +Package_DFN_QFN:Texas_RGE0024C_VQFN-24-1EP_4x4mm_P0.5mm_EP2.1x2.1mm_ThermalVias +Package_DFN_QFN:Texas_RGE0024H_VQFN-24-1EP_4x4mm_P0.5mm_EP2.7x2.7mm +Package_DFN_QFN:Texas_RGE0024H_VQFN-24-1EP_4x4mm_P0.5mm_EP2.7x2.7mm_ThermalVias +Package_DFN_QFN:Texas_RGP0020D_VQFN-20-1EP_4x4mm_P0.5mm_EP2.7x2.7mm +Package_DFN_QFN:Texas_RGP0020D_VQFN-20-1EP_4x4mm_P0.5mm_EP2.7x2.7mm_ThermalVias +Package_DFN_QFN:Texas_RGP0020H_VQFN-20-1EP_4x4mm_P0.5mm_EP2.4x2.4mm +Package_DFN_QFN:Texas_RGP0020H_VQFN-20-1EP_4x4mm_P0.5mm_EP2.4x2.4mm_ThermalVias +Package_DFN_QFN:Texas_RGV0016A_VQFN-16-1EP_4x4mm_P0.65mm_EP2.1x2.1mm +Package_DFN_QFN:Texas_RGV0016A_VQFN-16-1EP_4x4mm_P0.65mm_EP2.1x2.1mm_ThermalVias +Package_DFN_QFN:Texas_RGW0020A_VQFN-20-1EP_5x5mm_P0.65mm_EP3.15x3.15mm +Package_DFN_QFN:Texas_RGW0020A_VQFN-20-1EP_5x5mm_P0.65mm_EP3.15x3.15mm_ThermalVias Package_DFN_QFN:Texas_RGY_R-PVQFN-N16_EP2.05x2.55mm Package_DFN_QFN:Texas_RGY_R-PVQFN-N16_EP2.05x2.55mm_ThermalVias Package_DFN_QFN:Texas_RGY_R-PVQFN-N20_EP2.05x3.05mm Package_DFN_QFN:Texas_RGY_R-PVQFN-N20_EP2.05x3.05mm_ThermalVias Package_DFN_QFN:Texas_RGY_R-PVQFN-N24_EP2.05x3.1mm Package_DFN_QFN:Texas_RGY_R-PVQFN-N24_EP2.05x3.1mm_ThermalVias +Package_DFN_QFN:Texas_RGZ0048A_VQFN-48-1EP_7x7mm_P0.5mm_EP5.15x5.15mm +Package_DFN_QFN:Texas_RGZ0048A_VQFN-48-1EP_7x7mm_P0.5mm_EP5.15x5.15mm_ThermalVias +Package_DFN_QFN:Texas_RHA0040B_VQFN-40-1EP_6x6mm_P0.5mm_EP4.15x4.15mm +Package_DFN_QFN:Texas_RHA0040B_VQFN-40-1EP_6x6mm_P0.5mm_EP4.15x4.15mm_ThermalVias +Package_DFN_QFN:Texas_RHA0040D_VQFN-40-1EP_6x6mm_P0.5mm_EP2.9x2.9mm +Package_DFN_QFN:Texas_RHA0040D_VQFN-40-1EP_6x6mm_P0.5mm_EP2.9x2.9mm_ThermalVias +Package_DFN_QFN:Texas_RHA0040E_VQFN-40-1EP_6x6mm_P0.5mm_EP3.52x2.62mm +Package_DFN_QFN:Texas_RHA0040E_VQFN-40-1EP_6x6mm_P0.5mm_EP3.52x2.62mm_ThermalVias +Package_DFN_QFN:Texas_RHA_VQFN-40-1EP_6x6mm_P0.5mm_EP4.6x4.6mm +Package_DFN_QFN:Texas_RHA_VQFN-40-1EP_6x6mm_P0.5mm_EP4.6x4.6mm_ThermalVias +Package_DFN_QFN:Texas_RHB0032E_VQFN-32-1EP_5x5mm_P0.5mm_EP3.45x3.45mm +Package_DFN_QFN:Texas_RHB0032E_VQFN-32-1EP_5x5mm_P0.5mm_EP3.45x3.45mm_ThermalVias +Package_DFN_QFN:Texas_RHB0032M_VQFN-32-1EP_5x5mm_P0.5mm_EP2.1x2.1mm +Package_DFN_QFN:Texas_RHB0032M_VQFN-32-1EP_5x5mm_P0.5mm_EP2.1x2.1mm_ThermalVias +Package_DFN_QFN:Texas_RHH0036C_VQFN-36-1EP_6x6mm_P0.5mm_EP4.4x4.4mm +Package_DFN_QFN:Texas_RHH0036C_VQFN-36-1EP_6x6mm_P0.5mm_EP4.4x4.4mm_ThermalVias Package_DFN_QFN:Texas_RJE0020A_VQFN-20-1EP_3x3mm_P0.45mm_EP0.675x0.76mm Package_DFN_QFN:Texas_RJE0020A_VQFN-20-1EP_3x3mm_P0.45mm_EP0.675x0.76mm_ThermalVias +Package_DFN_QFN:Texas_RMG0012A_WQFN-12_1.8x1.8mm_P0.4mm +Package_DFN_QFN:Texas_RMQ0024A_WQFN-24-1EP_3x3mm_P0.4mm_EP1.9x1.9mm +Package_DFN_QFN:Texas_RMQ0024A_WQFN-24-1EP_3x3mm_P0.4mm_EP1.9x1.9mm_ThermalVias Package_DFN_QFN:Texas_RNN0018A -Package_DFN_QFN:Texas_RUM0016A_EP2.6x2.6mm -Package_DFN_QFN:Texas_RUM0016A_EP2.6x2.6mm_ThermalVias +Package_DFN_QFN:Texas_RNP0030B_WQFN-30-1EP_4x6mm_P0.5mm_EP1.8x4.5mm +Package_DFN_QFN:Texas_RNP0030B_WQFN-30-1EP_4x6mm_P0.5mm_EP1.8x4.5mm_ThermalVias +Package_DFN_QFN:Texas_RPU0010A_VQFN-HR-10_2x2mm_P0.5mm +Package_DFN_QFN:Texas_RSA_VQFN-16-1EP_4x4mm_P0.65mm_EP2.7x2.7mm +Package_DFN_QFN:Texas_RSA_VQFN-16-1EP_4x4mm_P0.65mm_EP2.7x2.7mm_ThermalVias +Package_DFN_QFN:Texas_RSN_WQFN-32-1EP_4x4mm_P0.4mm_EP2.8x2.8mm +Package_DFN_QFN:Texas_RSN_WQFN-32-1EP_4x4mm_P0.4mm_EP2.8x2.8mm_ThermalVias +Package_DFN_QFN:Texas_RSW0010A_UQFN-10_1.4x1.8mm_P0.4mm +Package_DFN_QFN:Texas_RTE0016D_WQFN-16-1EP_3x3mm_P0.5mm_EP0.8x0.8mm +Package_DFN_QFN:Texas_RTE0016D_WQFN-16-1EP_3x3mm_P0.5mm_EP0.8x0.8mm_ThermalVias +Package_DFN_QFN:Texas_RTE_WQFN-16-1EP_3x3mm_P0.5mm_EP1.2x0.8mm +Package_DFN_QFN:Texas_RTE_WQFN-16-1EP_3x3mm_P0.5mm_EP1.2x0.8mm_ThermalVias +Package_DFN_QFN:Texas_RTW_WQFN-24-1EP_4x4mm_P0.5mm_EP2.7x2.7mm +Package_DFN_QFN:Texas_RTW_WQFN-24-1EP_4x4mm_P0.5mm_EP2.7x2.7mm_ThermalVias +Package_DFN_QFN:Texas_RTY_WQFN-16-1EP_4x4mm_P0.65mm_EP2.1x2.1mm +Package_DFN_QFN:Texas_RTY_WQFN-16-1EP_4x4mm_P0.65mm_EP2.1x2.1mm_ThermalVias +Package_DFN_QFN:Texas_RUM0016A_WQFN-16-1EP_4x4mm_P0.65mm_EP2.6x2.6mm +Package_DFN_QFN:Texas_RUM0016A_WQFN-16-1EP_4x4mm_P0.65mm_EP2.6x2.6mm_ThermalVias +Package_DFN_QFN:Texas_RUN0010A_WQFN-10_2x2mm_P0.5mm +Package_DFN_QFN:Texas_RVA_VQFN-16-1EP_3.5x3.5mm_P0.5mm_EP2.14x2.14mm +Package_DFN_QFN:Texas_RVA_VQFN-16-1EP_3.5x3.5mm_P0.5mm_EP2.14x2.14mm_ThermalVias +Package_DFN_QFN:Texas_RVE0028A_VQFN-28-1EP_3.5x4.5mm_P0.4mm_EP2.1x3.1mm +Package_DFN_QFN:Texas_RVE0028A_VQFN-28-1EP_3.5x4.5mm_P0.4mm_EP2.1x3.1mm_ThermalVias Package_DFN_QFN:Texas_RWH0032A Package_DFN_QFN:Texas_RWH0032A_ThermalVias +Package_DFN_QFN:Texas_RWU0007A_VQFN-7_2x2mm_P0.5mm Package_DFN_QFN:Texas_S-PDSO-N10_EP1.2x2mm Package_DFN_QFN:Texas_S-PDSO-N10_EP1.2x2mm_ThermalVias Package_DFN_QFN:Texas_S-PVQFN-N14 Package_DFN_QFN:Texas_S-PVQFN-N14_ThermalVias -Package_DFN_QFN:Texas_S-PVQFN-N16_EP2.7x2.7mm -Package_DFN_QFN:Texas_S-PVQFN-N16_EP2.7x2.7mm_ThermalVias -Package_DFN_QFN:Texas_S-PVQFN-N20_EP2.4x2.4mm -Package_DFN_QFN:Texas_S-PVQFN-N20_EP2.4x2.4mm_ThermalVias -Package_DFN_QFN:Texas_S-PVQFN-N20_EP2.7x2.7mm -Package_DFN_QFN:Texas_S-PVQFN-N20_EP2.7x2.7mm_ThermalVias -Package_DFN_QFN:Texas_S-PVQFN-N20_EP3.15x3.15mm -Package_DFN_QFN:Texas_S-PVQFN-N20_EP3.15x3.15mm_ThermalVias -Package_DFN_QFN:Texas_S-PVQFN-N24_EP2.1x2.1mm -Package_DFN_QFN:Texas_S-PVQFN-N24_EP2.1x2.1mm_ThermalVias -Package_DFN_QFN:Texas_S-PVQFN-N32_EP3.45x3.45mm -Package_DFN_QFN:Texas_S-PVQFN-N32_EP3.45x3.45mm_ThermalVias -Package_DFN_QFN:Texas_S-PVQFN-N36_EP4.4x4.4mm -Package_DFN_QFN:Texas_S-PVQFN-N36_EP4.4x4.4mm_ThermalVias -Package_DFN_QFN:Texas_S-PVQFN-N40_EP2.9x2.9mm -Package_DFN_QFN:Texas_S-PVQFN-N40_EP2.9x2.9mm_ThermalVias -Package_DFN_QFN:Texas_S-PVQFN-N40_EP3.52x2.62mm -Package_DFN_QFN:Texas_S-PVQFN-N40_EP3.52x2.62mm_ThermalVias -Package_DFN_QFN:Texas_S-PVQFN-N40_EP4.15x4.15mm -Package_DFN_QFN:Texas_S-PVQFN-N40_EP4.15x4.15mm_ThermalVias -Package_DFN_QFN:Texas_S-PVQFN-N40_EP4.6x4.6mm -Package_DFN_QFN:Texas_S-PVQFN-N40_EP4.6x4.6mm_ThermalVias -Package_DFN_QFN:Texas_S-PVQFN-N48_EP5.15x5.15mm -Package_DFN_QFN:Texas_S-PVQFN-N48_EP5.15x5.15mm_ThermalVias -Package_DFN_QFN:Texas_S-PVQFN-N64_EP4.25x4.25mm -Package_DFN_QFN:Texas_S-PVQFN-N64_EP4.25x4.25mm_ThermalVias Package_DFN_QFN:Texas_S-PWQFN-N100_EP5.5x5.5mm Package_DFN_QFN:Texas_S-PWQFN-N100_EP5.5x5.5mm_ThermalVias -Package_DFN_QFN:Texas_S-PWQFN-N16_EP1.2x0.8mm -Package_DFN_QFN:Texas_S-PWQFN-N16_EP1.2x0.8mm_ThermalVias -Package_DFN_QFN:Texas_S-PWQFN-N16_EP2.1x2.1mm -Package_DFN_QFN:Texas_S-PWQFN-N16_EP2.1x2.1mm_ThermalVias Package_DFN_QFN:Texas_S-PWQFN-N20 -Package_DFN_QFN:Texas_S-PWQFN-N24_EP2.7x2.7mm -Package_DFN_QFN:Texas_S-PWQFN-N24_EP2.7x2.7mm_ThermalVias -Package_DFN_QFN:Texas_S-PWQFN-N32_EP2.8x2.8mm -Package_DFN_QFN:Texas_S-PWQFN-N32_EP2.8x2.8mm_ThermalVias Package_DFN_QFN:Texas_S-PX2QFN-14 Package_DFN_QFN:Texas_UQFN-10_1.5x2mm_P0.5mm Package_DFN_QFN:Texas_VQFN-HR-12_2x2.5mm_P0.5mm @@ -10278,11 +10870,11 @@ Package_DFN_QFN:Texas_VQFN-HR-12_2x2.5mm_P0.5mm_ThermalVias Package_DFN_QFN:Texas_VQFN-HR-20_3x2.5mm_P0.5mm_RQQ0011A Package_DFN_QFN:Texas_VQFN-RHL-20 Package_DFN_QFN:Texas_VQFN-RHL-20_ThermalVias -Package_DFN_QFN:Texas_VSON-HR-8_1.5x2mm_P0.5mm -Package_DFN_QFN:Texas_WQFN-10_2x2mm_P0.5mm +Package_DFN_QFN:Texas_VQFN-RNR0011A-11 Package_DFN_QFN:Texas_WQFN-MR-100_3x3-DapStencil Package_DFN_QFN:Texas_WQFN-MR-100_ThermalVias_3x3-DapStencil Package_DFN_QFN:Texas_X2QFN-12_1.6x1.6mm_P0.4mm +Package_DFN_QFN:Texas_X2QFN-RUE-12_1.4x2mm_P0.4mm Package_DFN_QFN:TQFN-16-1EP_3x3mm_P0.5mm_EP1.23x1.23mm Package_DFN_QFN:TQFN-16-1EP_3x3mm_P0.5mm_EP1.23x1.23mm_ThermalVias Package_DFN_QFN:TQFN-16-1EP_3x3mm_P0.5mm_EP1.6x1.6mm @@ -10318,8 +10910,11 @@ Package_DFN_QFN:TQFN-32-1EP_5x5mm_P0.5mm_EP3.4x3.4mm Package_DFN_QFN:TQFN-32-1EP_5x5mm_P0.5mm_EP3.4x3.4mm_ThermalVias Package_DFN_QFN:TQFN-40-1EP_5x5mm_P0.4mm_EP3.5x3.5mm Package_DFN_QFN:TQFN-40-1EP_5x5mm_P0.4mm_EP3.5x3.5mm_ThermalVias +Package_DFN_QFN:TQFN-44-1EP_7x7mm_P0.5mm_EP4.7x4.7mm +Package_DFN_QFN:TQFN-44-1EP_7x7mm_P0.5mm_EP4.7x4.7mm_ThermalVias Package_DFN_QFN:TQFN-48-1EP_7x7mm_P0.5mm_EP5.1x5.1mm Package_DFN_QFN:TQFN-48-1EP_7x7mm_P0.5mm_EP5.1x5.1mm_ThermalVias +Package_DFN_QFN:UDC-QFN-20-4EP_3x4mm_P0.5mm_EP0.41x0.25mm Package_DFN_QFN:UDFN-10_1.35x2.6mm_P0.5mm Package_DFN_QFN:UDFN-4-1EP_1x1mm_P0.65mm_EP0.48x0.48mm Package_DFN_QFN:UDFN-9_1.0x3.8mm_P0.5mm @@ -10332,6 +10927,9 @@ Package_DFN_QFN:UQFN-16-1EP_3x3mm_P0.5mm_EP1.75x1.75mm Package_DFN_QFN:UQFN-16-1EP_4x4mm_P0.65mm_EP2.6x2.6mm Package_DFN_QFN:UQFN-16-1EP_4x4mm_P0.65mm_EP2.6x2.6mm_ThermalVias Package_DFN_QFN:UQFN-16-1EP_4x4mm_P0.65mm_EP2.7x2.7mm +Package_DFN_QFN:UQFN-16_1.8x2.6mm_P0.4mm +Package_DFN_QFN:UQFN-20-1EP_3x3mm_P0.4mm_EP1.7x1.7mm +Package_DFN_QFN:UQFN-20-1EP_3x3mm_P0.4mm_EP1.7x1.7mm_ThermalVias Package_DFN_QFN:UQFN-20-1EP_3x3mm_P0.4mm_EP1.85x1.85mm Package_DFN_QFN:UQFN-20-1EP_3x3mm_P0.4mm_EP1.85x1.85mm_ThermalVias Package_DFN_QFN:UQFN-20-1EP_4x4mm_P0.5mm_EP2.8x2.8mm @@ -10348,6 +10946,10 @@ Package_DFN_QFN:UQFN-48-1EP_6x6mm_P0.4mm_EP4.62x4.62mm_ThermalVias Package_DFN_QFN:VDFN-8-1EP_2x2mm_P0.5mm_EP0.9x1.7mm Package_DFN_QFN:Vishay_PowerPAK_MLP44-24L Package_DFN_QFN:Vishay_PowerPAK_MLP44-24L_ThermalVias +Package_DFN_QFN:VQFN-100-1EP_12x12mm_P0.4mm_EP8x8mm +Package_DFN_QFN:VQFN-100-1EP_12x12mm_P0.4mm_EP8x8mm_ThermalVias +Package_DFN_QFN:VQFN-12-1EP_4x4mm_P0.8mm_EP2.1x2.1mm +Package_DFN_QFN:VQFN-12-1EP_4x4mm_P0.8mm_EP2.1x2.1mm_ThermalVias Package_DFN_QFN:VQFN-16-1EP_3x3mm_P0.5mm_EP1.1x1.1mm Package_DFN_QFN:VQFN-16-1EP_3x3mm_P0.5mm_EP1.1x1.1mm_ThermalVias Package_DFN_QFN:VQFN-16-1EP_3x3mm_P0.5mm_EP1.45x1.45mm @@ -10370,12 +10972,20 @@ Package_DFN_QFN:VQFN-28-1EP_4x4mm_P0.45mm_EP2.4x2.4mm Package_DFN_QFN:VQFN-28-1EP_4x4mm_P0.45mm_EP2.4x2.4mm_ThermalVias Package_DFN_QFN:VQFN-28-1EP_4x5mm_P0.5mm_EP2.55x3.55mm Package_DFN_QFN:VQFN-28-1EP_4x5mm_P0.5mm_EP2.55x3.55mm_ThermalVias +Package_DFN_QFN:VQFN-28-1EP_5x5mm_P0.5mm_EP3.25x3.25mm +Package_DFN_QFN:VQFN-28-1EP_5x5mm_P0.5mm_EP3.25x3.25mm_ThermalVias +Package_DFN_QFN:VQFN-32-1EP_4x4mm_P0.4mm_EP2.8x2.8mm +Package_DFN_QFN:VQFN-32-1EP_4x4mm_P0.4mm_EP2.8x2.8mm_ThermalVias Package_DFN_QFN:VQFN-32-1EP_5x5mm_P0.5mm_EP3.15x3.15mm Package_DFN_QFN:VQFN-32-1EP_5x5mm_P0.5mm_EP3.15x3.15mm_ThermalVias Package_DFN_QFN:VQFN-32-1EP_5x5mm_P0.5mm_EP3.1x3.1mm Package_DFN_QFN:VQFN-32-1EP_5x5mm_P0.5mm_EP3.1x3.1mm_ThermalVias Package_DFN_QFN:VQFN-32-1EP_5x5mm_P0.5mm_EP3.5x3.5mm Package_DFN_QFN:VQFN-32-1EP_5x5mm_P0.5mm_EP3.5x3.5mm_ThermalVias +Package_DFN_QFN:VQFN-40-1EP_5x5mm_P0.4mm_EP3.5x3.5mm +Package_DFN_QFN:VQFN-40-1EP_5x5mm_P0.4mm_EP3.5x3.5mm_ThermalVias +Package_DFN_QFN:VQFN-40-1EP_5x5mm_P0.4mm_EP3.6x3.6mm +Package_DFN_QFN:VQFN-40-1EP_5x5mm_P0.4mm_EP3.6x3.6mm_ThermalVias Package_DFN_QFN:VQFN-46-1EP_5x6mm_P0.4mm_EP2.8x3.8mm Package_DFN_QFN:VQFN-46-1EP_5x6mm_P0.4mm_EP2.8x3.8mm_ThermalVias Package_DFN_QFN:VQFN-48-1EP_6x6mm_P0.4mm_EP4.1x4.1mm @@ -10389,6 +10999,7 @@ Package_DFN_QFN:VQFN-64-1EP_9x9mm_P0.5mm_EP5.4x5.4mm_ThermalVias Package_DFN_QFN:VQFN-64-1EP_9x9mm_P0.5mm_EP7.15x7.15mm Package_DFN_QFN:VQFN-64-1EP_9x9mm_P0.5mm_EP7.15x7.15mm_ThermalVias Package_DFN_QFN:W-PDFN-8-1EP_6x5mm_P1.27mm_EP3x3mm +Package_DFN_QFN:WCH_QFN-16-1EP_3x3mm_P0.5mm_EP1.8x1.8mm Package_DFN_QFN:WDFN-10-1EP_3x3mm_P0.5mm_EP1.8x2.5mm Package_DFN_QFN:WDFN-10-1EP_3x3mm_P0.5mm_EP1.8x2.5mm_ThermalVias Package_DFN_QFN:WDFN-12-1EP_3x3mm_P0.45mm_EP1.7x2.5mm @@ -10399,7 +11010,10 @@ Package_DFN_QFN:WDFN-8-1EP_3x2mm_P0.5mm_EP1.3x1.4mm Package_DFN_QFN:WDFN-8-1EP_4x3mm_P0.65mm_EP2.4x1.8mm Package_DFN_QFN:WDFN-8-1EP_4x3mm_P0.65mm_EP2.4x1.8mm_ThermalVias Package_DFN_QFN:WDFN-8-1EP_6x5mm_P1.27mm_EP3.4x4mm +Package_DFN_QFN:WDFN-8-1EP_8x6mm_P1.27mm_EP6x4.8mm +Package_DFN_QFN:WDFN-8-1EP_8x6mm_P1.27mm_EP6x4.8mm_ThermalVias Package_DFN_QFN:WDFN-8_2x2mm_P0.5mm +Package_DFN_QFN:WFDFPN-8-1EP_3x2mm_P0.5mm_EP1.25x1.35mm Package_DFN_QFN:WQFN-14-1EP_2.5x2.5mm_P0.5mm_EP1.45x1.45mm Package_DFN_QFN:WQFN-14-1EP_2.5x2.5mm_P0.5mm_EP1.45x1.45mm_ThermalVias Package_DFN_QFN:WQFN-16-1EP_3x3mm_P0.5mm_EP1.68x1.68mm @@ -10420,6 +11034,38 @@ Package_DFN_QFN:WQFN-24-1EP_4x4mm_P0.5mm_EP2.6x2.6mm_ThermalVias Package_DFN_QFN:WQFN-32-1EP_5x5mm_P0.5mm_EP3.1x3.1mm Package_DFN_QFN:WQFN-42-1EP_3.5x9mm_P0.5mm_EP2.05x7.55mm Package_DFN_QFN:WQFN-42-1EP_3.5x9mm_P0.5mm_EP2.05x7.55mm_ThermalVias +Package_DIP:CERDIP-14_W7.62mm_SideBrazed +Package_DIP:CERDIP-14_W7.62mm_SideBrazed_LongPads +Package_DIP:CERDIP-14_W7.62mm_SideBrazed_LongPads_Socket +Package_DIP:CERDIP-14_W7.62mm_SideBrazed_Socket +Package_DIP:CERDIP-16_W7.62mm_SideBrazed +Package_DIP:CERDIP-16_W7.62mm_SideBrazed_LongPads +Package_DIP:CERDIP-16_W7.62mm_SideBrazed_LongPads_Socket +Package_DIP:CERDIP-16_W7.62mm_SideBrazed_Socket +Package_DIP:CERDIP-18_W7.62mm_SideBrazed +Package_DIP:CERDIP-18_W7.62mm_SideBrazed_LongPads +Package_DIP:CERDIP-18_W7.62mm_SideBrazed_LongPads_Socket +Package_DIP:CERDIP-18_W7.62mm_SideBrazed_Socket +Package_DIP:CERDIP-20_W7.62mm_SideBrazed +Package_DIP:CERDIP-20_W7.62mm_SideBrazed_LongPads +Package_DIP:CERDIP-20_W7.62mm_SideBrazed_LongPads_Socket +Package_DIP:CERDIP-20_W7.62mm_SideBrazed_Socket +Package_DIP:CERDIP-22_W7.62mm_SideBrazed +Package_DIP:CERDIP-22_W7.62mm_SideBrazed_LongPads +Package_DIP:CERDIP-22_W7.62mm_SideBrazed_LongPads_Socket +Package_DIP:CERDIP-22_W7.62mm_SideBrazed_Socket +Package_DIP:CERDIP-24_W7.62mm_SideBrazed +Package_DIP:CERDIP-24_W7.62mm_SideBrazed_LongPads +Package_DIP:CERDIP-24_W7.62mm_SideBrazed_LongPads_Socket +Package_DIP:CERDIP-24_W7.62mm_SideBrazed_Socket +Package_DIP:CERDIP-28_W7.62mm_SideBrazed +Package_DIP:CERDIP-28_W7.62mm_SideBrazed_LongPads +Package_DIP:CERDIP-28_W7.62mm_SideBrazed_LongPads_Socket +Package_DIP:CERDIP-28_W7.62mm_SideBrazed_Socket +Package_DIP:CERDIP-8_W7.62mm_SideBrazed +Package_DIP:CERDIP-8_W7.62mm_SideBrazed_LongPads +Package_DIP:CERDIP-8_W7.62mm_SideBrazed_LongPads_Socket +Package_DIP:CERDIP-8_W7.62mm_SideBrazed_Socket Package_DIP:DIP-10_W10.16mm Package_DIP:DIP-10_W10.16mm_LongPads Package_DIP:DIP-10_W7.62mm @@ -10476,6 +11122,11 @@ Package_DIP:DIP-22_W7.62mm_SMDSocket_SmallPads Package_DIP:DIP-22_W7.62mm_Socket Package_DIP:DIP-22_W7.62mm_Socket_LongPads Package_DIP:DIP-22_W8.89mm_SMDSocket_LongPads +Package_DIP:DIP-24_18.0mmx34.29mm_W15.24mm +Package_DIP:DIP-24_18.0mmx34.29mm_W15.24mm_LongPads +Package_DIP:DIP-24_18.0mmx34.29mm_W15.24mm_SMDSocket_SmallPads +Package_DIP:DIP-24_18.0mmx34.29mm_W15.24mm_Socket +Package_DIP:DIP-24_18.0mmx34.29mm_W15.24mm_Socket_LongPads Package_DIP:DIP-24_W10.16mm Package_DIP:DIP-24_W10.16mm_LongPads Package_DIP:DIP-24_W10.16mm_SMDSocket_SmallPads @@ -10593,6 +11244,8 @@ Package_DIP:DIP-8_W7.62mm_Socket Package_DIP:DIP-8_W7.62mm_Socket_LongPads Package_DIP:DIP-8_W8.89mm_SMDSocket_LongPads Package_DIP:Fairchild_LSOP-8 +Package_DIP:IXYS_Flatpak-8_6.3x9.7mm_P2.54mm +Package_DIP:IXYS_SMD-8_6.3x9.7mm_P2.54mm Package_DIP:PowerIntegrations_eDIP-12B Package_DIP:PowerIntegrations_PDIP-8B Package_DIP:PowerIntegrations_PDIP-8C @@ -10685,6 +11338,7 @@ Package_DirectFET:DirectFET_SH Package_DirectFET:DirectFET_SJ Package_DirectFET:DirectFET_SQ Package_DirectFET:DirectFET_ST +Package_LCC:Analog_LCC-8_5x5mm_P1.27mm Package_LCC:PLCC-20 Package_LCC:PLCC-20_SMD-Socket Package_LCC:PLCC-20_THT-Socket @@ -10731,9 +11385,11 @@ Package_LGA:LGA-8_8x6.2mm_P1.27mm Package_LGA:LGA-8_8x6mm_P1.27mm Package_LGA:Linear_LGA-133_15.0x15.0mm_Layout12x12_P1.27mm Package_LGA:MPS_LGA-18-10EP_12x12mm_P3.3mm +Package_LGA:Nordic_nRF9160-SIxx_LGA-102-59EP_16.0x10.5mm_P0.5mm Package_LGA:NXP_LGA-8_3x5mm_P1.25mm_H1.1mm Package_LGA:NXP_LGA-8_3x5mm_P1.25mm_H1.2mm -Package_LGA:Rohm_MLGA010V020A_LGA-10_2x2mm_P0.45mm_LayoutBorder_3x2y +Package_LGA:Rohm_MLGA010V020A_LGA-10_2x2mm_P0.45mm_LayoutBorder2x3y +Package_LGA:ST_CCLGA-7L_2.8x2.8mm_P1.15mm_H1.95mm Package_LGA:ST_HLGA-10_2.5x2.5mm_P0.6mm_LayoutBorder3x2y Package_LGA:ST_HLGA-10_2x2mm_P0.5mm_LayoutBorder3x2y Package_LGA:Texas_SIL0008D_MicroSiP-8-1EP_2.8x3mm_P0.65mm_EP1.1x1.9mm @@ -10756,6 +11412,8 @@ Package_QFP:HTQFP-64-1EP_10x10mm_P0.5mm_EP8x8mm_Mask4.4x4.4mm_ThermalVias Package_QFP:LQFP-100_14x14mm_P0.5mm Package_QFP:LQFP-128_14x14mm_P0.4mm Package_QFP:LQFP-128_14x20mm_P0.5mm +Package_QFP:LQFP-144-1EP_20x20mm_P0.5mm_EP6.5x6.5mm +Package_QFP:LQFP-144-1EP_20x20mm_P0.5mm_EP6.5x6.5mm_ThermalVias Package_QFP:LQFP-144_20x20mm_P0.5mm Package_QFP:LQFP-160_24x24mm_P0.5mm Package_QFP:LQFP-176_20x20mm_P0.4mm @@ -10783,6 +11441,7 @@ Package_QFP:LQFP-64_7x7mm_P0.4mm Package_QFP:LQFP-80_10x10mm_P0.4mm Package_QFP:LQFP-80_12x12mm_P0.5mm Package_QFP:LQFP-80_14x14mm_P0.65mm +Package_QFP:Microchip_PQFP-44_10x10mm_P0.8mm Package_QFP:MQFP-44_10x10mm_P0.8mm Package_QFP:PQFP-100_14x20mm_P0.65mm Package_QFP:PQFP-112_20x20mm_P0.65mm @@ -10790,11 +11449,13 @@ Package_QFP:PQFP-132_24x24mm_P0.635mm Package_QFP:PQFP-132_24x24mm_P0.635mm_i386 Package_QFP:PQFP-144_28x28mm_P0.65mm Package_QFP:PQFP-160_28x28mm_P0.65mm +Package_QFP:PQFP-168_28x28mm_P0.65mm Package_QFP:PQFP-208_28x28mm_P0.5mm Package_QFP:PQFP-240_32.1x32.1mm_P0.5mm Package_QFP:PQFP-256_28x28mm_P0.4mm Package_QFP:PQFP-32_5x5mm_P0.5mm Package_QFP:PQFP-44_10x10mm_P0.8mm +Package_QFP:PQFP-64_14x14mm_P0.8mm Package_QFP:PQFP-80_14x20mm_P0.8mm Package_QFP:Texas_PHP0048E_HTQFP-48-1EP_7x7mm_P0.5mm_EP6.5x6.5mm_Mask3.62x3.62mm Package_QFP:Texas_PHP0048E_HTQFP-48-1EP_7x7mm_P0.5mm_EP6.5x6.5mm_Mask3.62x3.62mm_ThermalVias @@ -10817,6 +11478,8 @@ Package_QFP:TQFP-48-1EP_7x7mm_P0.5mm_EP5x5mm_ThermalVias Package_QFP:TQFP-48_7x7mm_P0.5mm Package_QFP:TQFP-52-1EP_10x10mm_P0.65mm_EP6.5x6.5mm Package_QFP:TQFP-52-1EP_10x10mm_P0.65mm_EP6.5x6.5mm_ThermalVias +Package_QFP:TQFP-64-1EP_10x10mm_P0.5mm_EP5.305x5.305mm +Package_QFP:TQFP-64-1EP_10x10mm_P0.5mm_EP5.305x5.305mm_ThermalVias Package_QFP:TQFP-64-1EP_10x10mm_P0.5mm_EP8x8mm Package_QFP:TQFP-64_10x10mm_P0.5mm Package_QFP:TQFP-64_14x14mm_P0.8mm @@ -10844,6 +11507,9 @@ Package_SIP:SIP9_Housing_BigPads Package_SIP:SLA704XM Package_SIP:STK672-040-E Package_SIP:STK672-080-E +Package_SO:Analog_MSOP-12-16-1EP_3x4.039mm_P0.5mm_EP1.651x2.845mm +Package_SO:Analog_MSOP-12-16-1EP_3x4.039mm_P0.5mm_EP1.651x2.845mm_ThermalVias +Package_SO:Analog_MSOP-12-16_3x4.039mm_P0.5mm Package_SO:Diodes_PSOP-8 Package_SO:Diodes_SO-8EP Package_SO:ETSSOP-20-1EP_4.4x6.5mm_P0.65mm_EP3x4.2mm @@ -10893,8 +11559,14 @@ Package_SO:HTSSOP-28-1EP_4.4x9.7mm_P0.65mm_EP3.4x9.5mm_Mask2.4x6.17mm_ThermalVia Package_SO:HTSSOP-28-1EP_4.4x9.7mm_P0.65mm_EP3.4x9.5mm_ThermalVias Package_SO:HTSSOP-32-1EP_6.1x11mm_P0.65mm_EP5.2x11mm_Mask4.11x4.36mm Package_SO:HTSSOP-32-1EP_6.1x11mm_P0.65mm_EP5.2x11mm_Mask4.11x4.36mm_ThermalVias +Package_SO:HTSSOP-38-1EP_4.4x9.7mm_P0.5mm_EP1.5x3.3mm +Package_SO:HTSSOP-38-1EP_4.4x9.7mm_P0.5mm_EP1.5x3.3mm_ThermalVias +Package_SO:HTSSOP-38-1EP_4.4x9.7mm_P0.5mm_EP2.74x4.75mm +Package_SO:HTSSOP-38-1EP_4.4x9.7mm_P0.5mm_EP2.74x4.75mm_ThermalVias Package_SO:HTSSOP-38-1EP_6.1x12.5mm_P0.65mm_EP5.2x12.5mm_Mask3.39x6.35mm Package_SO:HTSSOP-38-1EP_6.1x12.5mm_P0.65mm_EP5.2x12.5mm_Mask3.39x6.35mm_ThermalVias +Package_SO:HTSSOP-44-1EP_6.1x14mm_P0.635mm_EP5.2x14mm_Mask4.31x8.26mm +Package_SO:HTSSOP-44-1EP_6.1x14mm_P0.635mm_EP5.2x14mm_Mask4.31x8.26mm_ThermalVias Package_SO:HTSSOP-44_6.1x14mm_P0.635mm_TopEP4.14x7.01mm Package_SO:HTSSOP-56-1EP_6.1x14mm_P0.5mm_EP3.61x6.35mm Package_SO:HVSSOP-10-1EP_3x3mm_P0.5mm_EP1.57x1.88mm @@ -10912,12 +11584,15 @@ Package_SO:Infineon_PG-DSO-20-85 Package_SO:Infineon_PG-DSO-20-85_ThermalVias Package_SO:Infineon_PG-DSO-20-87 Package_SO:Infineon_PG-DSO-20-U03_7.5x12.8mm +Package_SO:Infineon_PG-DSO-8-24_4x5mm Package_SO:Infineon_PG-DSO-8-27_3.9x4.9mm_EP2.65x3mm Package_SO:Infineon_PG-DSO-8-27_3.9x4.9mm_EP2.65x3mm_ThermalVias Package_SO:Infineon_PG-DSO-8-43 +Package_SO:Infineon_PG-DSO-8-59_7.5x6.3mm Package_SO:Infineon_PG-TSDSO-14-22 -Package_SO:Linear_MSOP-12-16-1EP_3x4mm_P0.5mm -Package_SO:Linear_MSOP-12-16_3x4mm_P0.5mm +Package_SO:Infineon_SOIC-20W_7.6x12.8mm_P1.27mm +Package_SO:Linear_HTSSOP-31-38-1EP_4.4x9.7mm_P0.5mm_EP2.74x4.75mm +Package_SO:Linear_HTSSOP-31-38-1EP_4.4x9.7mm_P0.5mm_EP2.74x4.75mm_ThermalVias Package_SO:MFSOP6-4_4.4x3.6mm_P1.27mm Package_SO:MFSOP6-5_4.4x3.6mm_P1.27mm Package_SO:MSOP-10-1EP_3x3mm_P0.5mm_EP1.68x1.88mm @@ -10927,18 +11602,12 @@ Package_SO:MSOP-10-1EP_3x3mm_P0.5mm_EP1.73x1.98mm_ThermalVias Package_SO:MSOP-10-1EP_3x3mm_P0.5mm_EP2.2x3.1mm_Mask1.83x1.89mm Package_SO:MSOP-10-1EP_3x3mm_P0.5mm_EP2.2x3.1mm_Mask1.83x1.89mm_ThermalVias Package_SO:MSOP-10_3x3mm_P0.5mm -Package_SO:MSOP-12-16-1EP_3x4mm_P0.5mm_EP1.65x2.85mm -Package_SO:MSOP-12-16-1EP_3x4mm_P0.5mm_EP1.65x2.85mm_ThermalVias -Package_SO:MSOP-12-16_3x4mm_P0.5mm -Package_SO:MSOP-12-1EP_3x4mm_P0.65mm_EP1.65x2.85mm -Package_SO:MSOP-12-1EP_3x4mm_P0.65mm_EP1.65x2.85mm_ThermalVias -Package_SO:MSOP-12_3x4mm_P0.65mm +Package_SO:MSOP-12-1EP_3x4.039mm_P0.65mm_EP1.651x2.845mm +Package_SO:MSOP-12-1EP_3x4.039mm_P0.65mm_EP1.651x2.845mm_ThermalVias +Package_SO:MSOP-12_3x4.039mm_P0.65mm Package_SO:MSOP-16-1EP_3x4.039mm_P0.5mm_EP1.651x2.845mm Package_SO:MSOP-16-1EP_3x4.039mm_P0.5mm_EP1.651x2.845mm_ThermalVias -Package_SO:MSOP-16-1EP_3x4mm_P0.5mm_EP1.65x2.85mm -Package_SO:MSOP-16-1EP_3x4mm_P0.5mm_EP1.65x2.85mm_ThermalVias Package_SO:MSOP-16_3x4.039mm_P0.5mm -Package_SO:MSOP-16_3x4mm_P0.5mm Package_SO:MSOP-8-1EP_3x3mm_P0.65mm_EP1.5x1.8mm Package_SO:MSOP-8-1EP_3x3mm_P0.65mm_EP1.5x1.8mm_ThermalVias Package_SO:MSOP-8-1EP_3x3mm_P0.65mm_EP1.68x1.88mm @@ -10950,6 +11619,8 @@ Package_SO:MSOP-8-1EP_3x3mm_P0.65mm_EP1.95x2.15mm_ThermalVias Package_SO:MSOP-8-1EP_3x3mm_P0.65mm_EP2.5x3mm_Mask1.73x2.36mm Package_SO:MSOP-8-1EP_3x3mm_P0.65mm_EP2.5x3mm_Mask1.73x2.36mm_ThermalVias Package_SO:MSOP-8_3x3mm_P0.65mm +Package_SO:NXP_HTSSOP-28-1EP_4.4x9.7mm_P0.65mm_EP2.2x3.4mm +Package_SO:NXP_HTSSOP-28-1EP_4.4x9.7mm_P0.65mm_EP2.2x3.4mm_ThermalVias Package_SO:OnSemi_Micro8 Package_SO:ONSemi_SO-8FL_488AA Package_SO:PowerIntegrations_eSOP-12B @@ -10959,11 +11630,14 @@ Package_SO:PowerIntegrations_SO-8C Package_SO:PowerPAK_SO-8L_Single Package_SO:PowerPAK_SO-8_Dual Package_SO:PowerPAK_SO-8_Single +Package_SO:PowerSSO-16-1EP_3.9x4.9mm_P0.5mm_EP2.5x3.61mm +Package_SO:PowerSSO-16-1EP_3.9x4.9mm_P0.5mm_EP2.5x3.61mm_ThermalVias Package_SO:PSOP-44_16.9x27.17mm_P1.27mm Package_SO:QSOP-16_3.9x4.9mm_P0.635mm Package_SO:QSOP-20_3.9x8.7mm_P0.635mm Package_SO:QSOP-24_3.9x8.7mm_P0.635mm -Package_SO:SC-74-6_1.5x2.9mm_P0.95mm +Package_SO:QSOP-28_3.9x9.9mm_P0.635mm +Package_SO:Renesas_SOP-32_11.4x20.75mm_P1.27mm Package_SO:SO-14_3.9x8.65mm_P1.27mm Package_SO:SO-14_5.3x10.2mm_P1.27mm Package_SO:SO-16_3.9x9.9mm_P1.27mm @@ -10978,11 +11652,12 @@ Package_SO:SO-4_4.4x3.6mm_P2.54mm Package_SO:SO-4_4.4x3.9mm_P2.54mm Package_SO:SO-4_4.4x4.3mm_P2.54mm Package_SO:SO-4_7.6x3.6mm_P2.54mm +Package_SO:SO-5-6_4.55x3.7mm_P1.27mm Package_SO:SO-5_4.4x3.6mm_P1.27mm Package_SO:SO-6L_10x3.84mm_P1.27mm Package_SO:SO-6_4.4x3.6mm_P1.27mm Package_SO:SO-8_3.9x4.9mm_P1.27mm -Package_SO:SO-8_5.3x6.2mm_P1.27mm +Package_SO:SOIC-10_3.9x4.9mm_P1mm Package_SO:SOIC-14-16_3.9x9.9mm_P1.27mm Package_SO:SOIC-14W_7.5x9mm_P1.27mm Package_SO:SOIC-14_3.9x8.7mm_P1.27mm @@ -10998,6 +11673,7 @@ Package_SO:SOIC-20W_7.5x15.4mm_P1.27mm Package_SO:SOIC-24W_7.5x15.4mm_P1.27mm Package_SO:SOIC-28W_7.5x17.9mm_P1.27mm Package_SO:SOIC-28W_7.5x18.7mm_P1.27mm +Package_SO:SOIC-32_7.518x20.777mm_P1.27mm Package_SO:SOIC-4_4.55x2.6mm_P1.27mm Package_SO:SOIC-4_4.55x3.7mm_P2.54mm Package_SO:SOIC-8-1EP_3.9x4.9mm_P1.27mm_EP2.29x3mm @@ -11013,36 +11689,49 @@ Package_SO:SOIC-8-1EP_3.9x4.9mm_P1.27mm_EP2.62x3.51mm_ThermalVias Package_SO:SOIC-8-1EP_3.9x4.9mm_P1.27mm_EP2.95x4.9mm_Mask2.71x3.4mm Package_SO:SOIC-8-1EP_3.9x4.9mm_P1.27mm_EP2.95x4.9mm_Mask2.71x3.4mm_ThermalVias Package_SO:SOIC-8-N7_3.9x4.9mm_P1.27mm -Package_SO:SOIC-8W_5.3x5.3mm_P1.27mm Package_SO:SOIC-8_3.9x4.9mm_P1.27mm -Package_SO:SOIC-8_5.23x5.23mm_P1.27mm -Package_SO:SOIC-8_5.275x5.275mm_P1.27mm +Package_SO:SOIC-8_5.3x5.3mm_P1.27mm +Package_SO:SOIC-8_5.3x6.2mm_P1.27mm Package_SO:SOIC-8_7.5x5.85mm_P1.27mm -Package_SO:SOJ-36_10.16x23.49mm_P1.27mm +Package_SO:SOJ-24_7.62x15.875mm_P1.27mm +Package_SO:SOJ-28_10.16x18.415mm_P1.27mm +Package_SO:SOJ-28_7.62x18.415mm_P1.27mm +Package_SO:SOJ-32_10.16x20.955mm_P1.27mm +Package_SO:SOJ-32_7.62x20.955mm_P1.27mm +Package_SO:SOJ-36_10.16x23.495mm_P1.27mm +Package_SO:SOJ-44_10.16x28.575mm_P1.27mm Package_SO:SOP-16_3.9x9.9mm_P1.27mm Package_SO:SOP-16_4.4x10.4mm_P1.27mm Package_SO:SOP-16_4.55x10.3mm_P1.27mm +Package_SO:SOP-18_7.495x11.515mm_P1.27mm Package_SO:SOP-18_7x12.5mm_P1.27mm Package_SO:SOP-20_7.5x12.8mm_P1.27mm Package_SO:SOP-24_7.5x15.4mm_P1.27mm +Package_SO:SOP-28_8.4x18.16mm_P1.27mm +Package_SO:SOP-32_11.305x20.495mm_P1.27mm +Package_SO:SOP-44_12.6x28.5mm_P1.27mm +Package_SO:SOP-44_13.3x28.2mm_P1.27mm Package_SO:SOP-4_3.8x4.1mm_P2.54mm Package_SO:SOP-4_4.4x2.6mm_P1.27mm +Package_SO:SOP-4_7.5x4.1mm_P2.54mm Package_SO:SOP-8-1EP_4.57x4.57mm_P1.27mm_EP4.57x4.45mm Package_SO:SOP-8-1EP_4.57x4.57mm_P1.27mm_EP4.57x4.45mm_ThermalVias Package_SO:SOP-8_3.76x4.96mm_P1.27mm Package_SO:SOP-8_3.9x4.9mm_P1.27mm -Package_SO:SOP-8_5.28x5.23mm_P1.27mm Package_SO:SOP-8_6.605x9.655mm_P2.54mm Package_SO:SOP-8_6.62x9.15mm_P2.54mm Package_SO:SSO-4_6.7x5.1mm_P2.54mm_Clearance8mm Package_SO:SSO-6_6.8x4.6mm_P1.27mm_Clearance7mm Package_SO:SSO-6_6.8x4.6mm_P1.27mm_Clearance8mm Package_SO:SSO-7-8_6.4x9.78mm_P2.54mm +Package_SO:SSO-8-7_6.4x9.7mm_P2.54mm Package_SO:SSO-8_13.6x6.3mm_P1.27mm_Clearance14.2mm Package_SO:SSO-8_6.7x9.8mm_P2.54mm_Clearance8mm Package_SO:SSO-8_6.8x5.9mm_P1.27mm_Clearance7mm Package_SO:SSO-8_6.8x5.9mm_P1.27mm_Clearance8mm Package_SO:SSO-8_9.6x6.3mm_P1.27mm_Clearance10.5mm +Package_SO:SSOP-10-1EP_3.9x4.9mm_P1mm_EP2.1x3.3mm +Package_SO:SSOP-10-1EP_3.9x4.9mm_P1mm_EP2.1x3.3mm_ThermalVias Package_SO:SSOP-10_3.9x4.9mm_P1.00mm Package_SO:SSOP-14_5.3x6.2mm_P0.65mm Package_SO:SSOP-16_3.9x4.9mm_P0.635mm @@ -11056,10 +11745,10 @@ Package_SO:SSOP-24_3.9x8.7mm_P0.635mm Package_SO:SSOP-24_5.3x8.2mm_P0.65mm Package_SO:SSOP-28_3.9x9.9mm_P0.635mm Package_SO:SSOP-28_5.3x10.2mm_P0.65mm -Package_SO:SSOP-32_11.305x20.495mm_P1.27mm Package_SO:SSOP-44_5.3x12.8mm_P0.5mm Package_SO:SSOP-48_5.3x12.8mm_P0.5mm Package_SO:SSOP-48_7.5x15.9mm_P0.635mm +Package_SO:SSOP-4_4.4x2.6mm_P1.27mm Package_SO:SSOP-56_7.5x18.5mm_P0.635mm Package_SO:SSOP-8_2.95x2.8mm_P0.65mm Package_SO:SSOP-8_3.95x5.21x3.27mm_P1.27mm @@ -11073,13 +11762,25 @@ Package_SO:ST_PowerSSO-24_SlugUp Package_SO:ST_PowerSSO-36_SlugDown Package_SO:ST_PowerSSO-36_SlugDown_ThermalVias Package_SO:ST_PowerSSO-36_SlugUp +Package_SO:Texas_DAD0032A_HTSSOP-32_6.1x11mm_P0.65mm_TopEP3.71x3.81mm +Package_SO:Texas_DGN0008B_VSSOP-8-1EP_3x3mm_P0.65mm_EP2x3mm_Mask1.88x1.98mm +Package_SO:Texas_DGN0008B_VSSOP-8-1EP_3x3mm_P0.65mm_EP2x3mm_Mask1.88x1.98mm_ThermalVias +Package_SO:Texas_DGN0008D_VSSOP-8-1EP_3x3mm_P0.65mm_EP2x2.94mm_Mask1.57x1.89mm +Package_SO:Texas_DGN0008D_VSSOP-8-1EP_3x3mm_P0.65mm_EP2x2.94mm_Mask1.57x1.89mm_ThermalVias +Package_SO:Texas_DGN0008G_VSSOP-8-1EP_3x3mm_P0.65mm_EP2x2.94mm_Mask1.846x2.15mm +Package_SO:Texas_DGN0008G_VSSOP-8-1EP_3x3mm_P0.65mm_EP2x2.94mm_Mask1.846x2.15mm_ThermalVias +Package_SO:Texas_DKD0036A_HSSOP-36_11x15.9mm_P0.65mm_TopEP5.85x12.65mm +Package_SO:Texas_DYY0016A_TSOT-23-16_4.2x2.0mm_P0.5mm Package_SO:Texas_HSOP-8-1EP_3.9x4.9mm_P1.27mm Package_SO:Texas_HSOP-8-1EP_3.9x4.9mm_P1.27mm_ThermalVias Package_SO:Texas_HTSOP-8-1EP_3.9x4.9mm_P1.27mm_EP2.95x4.9mm_Mask2.4x3.1mm_ThermalVias +Package_SO:Texas_PW0020A_TSSOP-20_4.4x6.5mm_P0.65mm Package_SO:Texas_PWP0020A +Package_SO:Texas_PWP0028V_TSSOP-28-1EP_4.4x9.7mm_P0.65mm_EP3.4x9.7mm_Mask2.94x5.62mm +Package_SO:Texas_PWP0028V_TSSOP-28-1EP_4.4x9.7mm_P0.65mm_EP3.4x9.7mm_Mask2.94x5.62mm_ThermalVias Package_SO:Texas_R-PDSO-G8_EP2.95x4.9mm_Mask2.4x3.1mm Package_SO:Texas_R-PDSO-G8_EP2.95x4.9mm_Mask2.4x3.1mm_ThermalVias -Package_SO:Texas_R-PDSO-N5 +Package_SO:Texas_S-PDSO-G8_3x3mm_P0.65mm Package_SO:TI_SO-PowerPAD-8 Package_SO:TI_SO-PowerPAD-8_ThermalVias Package_SO:TSOP-5_1.65x3.05mm_P0.95mm @@ -11107,6 +11808,7 @@ Package_SO:TSOP-I-56_14.4x14mm_P0.5mm Package_SO:TSOP-I-56_16.4x14mm_P0.5mm Package_SO:TSOP-I-56_18.4x14mm_P0.5mm Package_SO:TSOP-II-32_21.0x10.2mm_P1.27mm +Package_SO:TSOP-II-40-44_10.16x18.37mm_P0.8mm Package_SO:TSOP-II-44_10.16x18.41mm_P0.8mm Package_SO:TSOP-II-54_22.2x10.16mm_P0.8mm Package_SO:TSSOP-100_6.1x20.8mm_P0.4mm @@ -11128,8 +11830,13 @@ Package_SO:TSSOP-24_4.4x5mm_P0.4mm Package_SO:TSSOP-24_4.4x6.5mm_P0.5mm Package_SO:TSSOP-24_4.4x7.8mm_P0.65mm Package_SO:TSSOP-24_6.1x7.8mm_P0.65mm -Package_SO:TSSOP-28-1EP_4.4x9.7mm_P0.65mm +Package_SO:TSSOP-28-1EP_4.4x9.7mm_P0.65mm_EP2.74x4.75mm +Package_SO:TSSOP-28-1EP_4.4x9.7mm_P0.65mm_EP2.74x4.75mm_ThermalVias Package_SO:TSSOP-28-1EP_4.4x9.7mm_P0.65mm_EP2.85x6.7mm +Package_SO:TSSOP-28-1EP_4.4x9.7mm_P0.65mm_EP3.05x7.56mm +Package_SO:TSSOP-28-1EP_4.4x9.7mm_P0.65mm_EP3.05x7.56mm_ThermalVias +Package_SO:TSSOP-28-1EP_4.4x9.7mm_P0.65mm_EP3.4x9.7mm_Mask3.1x4.05mm +Package_SO:TSSOP-28-1EP_4.4x9.7mm_P0.65mm_EP3.4x9.7mm_Mask3.1x4.05mm_ThermalVias Package_SO:TSSOP-28_4.4x7.8mm_P0.5mm Package_SO:TSSOP-28_4.4x9.7mm_P0.65mm Package_SO:TSSOP-28_6.1x7.8mm_P0.5mm @@ -11184,11 +11891,11 @@ Package_SO:VSO-40_7.6x15.4mm_P0.762mm Package_SO:VSO-56_11.1x21.5mm_P0.75mm Package_SO:VSSOP-10_3x3mm_P0.5mm Package_SO:VSSOP-8_2.3x2mm_P0.5mm -Package_SO:VSSOP-8_2.4x2.1mm_P0.5mm -Package_SO:VSSOP-8_3.0x3.0mm_P0.65mm +Package_SO:VSSOP-8_3x3mm_P0.65mm Package_SO:Zetex_SM8 Package_SON:Diodes_PowerDI3333-8 Package_SON:Diodes_PowerDI3333-8_UXC_3.3x3.3mm_P0.65mm +Package_SON:EPSON_CE-USON-10_USON-10_3.2x2.5mm_P0.7mm Package_SON:Fairchild_DualPower33-6_3x3mm Package_SON:Fairchild_MicroPak-6_1.0x1.45mm_P0.5mm Package_SON:Fairchild_MicroPak2-6_1.0x1.0mm_P0.35mm @@ -11201,6 +11908,7 @@ Package_SON:Infineon_PG-TISON-8-2 Package_SON:Infineon_PG-TISON-8-3 Package_SON:Infineon_PG-TISON-8-4 Package_SON:Infineon_PG-TISON-8-5 +Package_SON:MicroCrystal_C7_SON-8_1.5x3.2mm_P0.9mm Package_SON:Nexperia_HUSON-12_USON-12-1EP_1.35x2.5mm_P0.4mm_EP0.4x2mm Package_SON:Nexperia_HUSON-16_USON-16-1EP_1.35x3.3mm_P0.4mm_EP0.4x2.8mm Package_SON:Nexperia_HUSON-8_USON-8-1EP_1.35x1.7mm_P0.4mm_EP0.4x1.2mm @@ -11208,11 +11916,15 @@ Package_SON:NXP_XSON-16 Package_SON:ROHM_VML0806 Package_SON:RTC_SMD_MicroCrystal_C3_2.5x3.7mm Package_SON:SON-8-1EP_3x2mm_P0.5mm_EP1.4x1.6mm +Package_SON:ST_PowerFLAT-6L_5x6mm_P1.27mm +Package_SON:ST_PowerFLAT_HV-5_8x8mm +Package_SON:ST_PowerFLAT_HV-8_5x6mm Package_SON:Texas_DPY0002A_0.6x1mm_P0.65mm Package_SON:Texas_DQK Package_SON:Texas_DQX002A Package_SON:Texas_DRC0010J Package_SON:Texas_DRC0010J_ThermalVias +Package_SON:Texas_DRX_WSON-10_2.5x2.5mm_P0.5mm Package_SON:Texas_DSC0010J Package_SON:Texas_DSC0010J_ThermalVias Package_SON:Texas_PWSON-N6 @@ -11227,7 +11939,9 @@ Package_SON:Texas_S-PVSON-N8_ThermalVias Package_SON:Texas_S-PWSON-N10 Package_SON:Texas_S-PWSON-N10_ThermalVias Package_SON:Texas_S-PWSON-N8_EP1.2x2mm +Package_SON:Texas_S-PWSON-N8_EP1.2x2mm_ThermalVias Package_SON:Texas_USON-6_1x1.45mm_P0.5mm_SMD +Package_SON:Texas_VSON-HR-8_1.5x2mm_P0.5mm Package_SON:Texas_X2SON-4_1x1mm_P0.65mm Package_SON:Texas_X2SON-5_0.8x0.8mm_P0.48mm Package_SON:Texas_X2SON-5_0.8x0.8mm_P0.48mm_RoutingVia @@ -11237,18 +11951,27 @@ Package_SON:VSON-10-1EP_3x3mm_P0.5mm_EP1.2x2mm Package_SON:VSON-10-1EP_3x3mm_P0.5mm_EP1.2x2mm_ThermalVias Package_SON:VSON-10-1EP_3x3mm_P0.5mm_EP1.65x2.4mm Package_SON:VSON-10-1EP_3x3mm_P0.5mm_EP1.65x2.4mm_ThermalVias +Package_SON:VSON-14-1EP_3x4.45mm_P0.65mm_EP1.6x4.2mm +Package_SON:VSON-14-1EP_3x4.45mm_P0.65mm_EP1.6x4.2mm_ThermalVias Package_SON:VSON-8-1EP_3x3mm_P0.65mm_EP1.65x2.4mm Package_SON:VSON-8-1EP_3x3mm_P0.65mm_EP1.65x2.4mm_ThermalVias +Package_SON:VSON-8-1EP_3x3mm_P0.65mm_EP1.6x2.4mm Package_SON:VSON-8_1.5x2mm_P0.5mm Package_SON:VSON-8_3.3x3.3mm_P0.65mm_NexFET Package_SON:VSONP-8-1EP_5x6_P1.27mm +Package_SON:Winbond_USON-8-1EP_3x2mm_P0.5mm_EP0.2x1.6mm +Package_SON:Winbond_USON-8-2EP_3x4mm_P0.8mm_EP0.2x0.8mm Package_SON:WSON-10-1EP_2.5x2.5mm_P0.5mm_EP1.2x2mm Package_SON:WSON-10-1EP_2.5x2.5mm_P0.5mm_EP1.2x2mm_ThermalVias Package_SON:WSON-10-1EP_2x3mm_P0.5mm_EP0.84x2.4mm Package_SON:WSON-10-1EP_2x3mm_P0.5mm_EP0.84x2.4mm_ThermalVias Package_SON:WSON-10-1EP_4x3mm_P0.5mm_EP2.2x2mm +Package_SON:WSON-10-1EP_4x4mm_P0.8mm_EP2.6x3mm +Package_SON:WSON-10-1EP_4x4mm_P0.8mm_EP2.6x3mm_ThermalVias Package_SON:WSON-12-1EP_3x2mm_P0.5mm_EP1x2.65 Package_SON:WSON-12-1EP_3x2mm_P0.5mm_EP1x2.65_ThermalVias +Package_SON:WSON-12-1EP_3x3mm_P0.5mm_EP1.5x2.5mm +Package_SON:WSON-12-1EP_3x3mm_P0.5mm_EP1.5x2.5mm_ThermalVias Package_SON:WSON-12-1EP_4x4mm_P0.5mm_EP2.6x3mm Package_SON:WSON-12-1EP_4x4mm_P0.5mm_EP2.6x3mm_ThermalVias Package_SON:WSON-14-1EP_4.0x4.0mm_P0.5mm_EP2.6x2.6mm @@ -11294,16 +12017,20 @@ Package_TO_SOT_SMD:Infineon_PG-TO-220-7Lead_TabPin8 Package_TO_SOT_SMD:Infineon_PG-TSFP-3-1 Package_TO_SOT_SMD:LFPAK33 Package_TO_SOT_SMD:LFPAK56 +Package_TO_SOT_SMD:LFPAK88 Package_TO_SOT_SMD:Nexperia_CFP15_SOT-1289 Package_TO_SOT_SMD:OnSemi_ECH8 Package_TO_SOT_SMD:PowerMacro_M234_NoHole Package_TO_SOT_SMD:PowerMacro_M234_WithHole Package_TO_SOT_SMD:PQFN_8x8 Package_TO_SOT_SMD:Rohm_HRP7 +Package_TO_SOT_SMD:ROHM_SOT-457_ClockwisePinNumbering Package_TO_SOT_SMD:SC-59 Package_TO_SOT_SMD:SC-59_Handsoldering Package_TO_SOT_SMD:SC-70-8 Package_TO_SOT_SMD:SC-70-8_Handsoldering +Package_TO_SOT_SMD:SC-74-6_1.55x2.9mm_P0.95mm +Package_TO_SOT_SMD:SC-74A-5_1.55x2.9mm_P0.95mm Package_TO_SOT_SMD:SC-82AA Package_TO_SOT_SMD:SC-82AA_Handsoldering Package_TO_SOT_SMD:SC-82AB @@ -11457,12 +12184,12 @@ Package_TO_SOT_THT:TO-218-3_Vertical Package_TO_SOT_THT:TO-220-11_P3.4x2.54mm_StaggerEven_Lead5.84mm_TabDown Package_TO_SOT_THT:TO-220-11_P3.4x2.54mm_StaggerOdd_Lead5.84mm_TabDown Package_TO_SOT_THT:TO-220-11_P3.4x5.08mm_StaggerEven_Lead4.58mm_Vertical -Package_TO_SOT_THT:TO-220-11_P3.4x5.08mm_StaggerOdd_Lead4.85mm_Vertical +Package_TO_SOT_THT:TO-220-11_P3.4x5.08mm_StaggerOdd_Lead4.58mm_Vertical Package_TO_SOT_THT:TO-220-11_P3.4x5.08mm_StaggerOdd_Lead8.45mm_TabDown -Package_TO_SOT_THT:TO-220-15_P2.54x2.54mm_StaggerEven_Lead4.58mm_Vertical Package_TO_SOT_THT:TO-220-15_P2.54x2.54mm_StaggerEven_Lead5.84mm_TabDown -Package_TO_SOT_THT:TO-220-15_P2.54x2.54mm_StaggerOdd_Lead4.58mm_Vertical Package_TO_SOT_THT:TO-220-15_P2.54x2.54mm_StaggerOdd_Lead5.84mm_TabDown +Package_TO_SOT_THT:TO-220-15_P2.54x5.08mm_StaggerEven_Lead4.58mm_Vertical +Package_TO_SOT_THT:TO-220-15_P2.54x5.08mm_StaggerOdd_Lead4.58mm_Vertical Package_TO_SOT_THT:TO-220-2_Horizontal_TabDown Package_TO_SOT_THT:TO-220-2_Horizontal_TabUp Package_TO_SOT_THT:TO-220-2_Vertical @@ -11471,10 +12198,10 @@ Package_TO_SOT_THT:TO-220-3_Horizontal_TabUp Package_TO_SOT_THT:TO-220-3_Vertical Package_TO_SOT_THT:TO-220-4_Horizontal_TabDown Package_TO_SOT_THT:TO-220-4_Horizontal_TabUp -Package_TO_SOT_THT:TO-220-4_P5.08x2.54mm_StaggerEven_Lead3.8mm_Vertical -Package_TO_SOT_THT:TO-220-4_P5.08x2.54mm_StaggerEven_Lead5.84mm_TabDown -Package_TO_SOT_THT:TO-220-4_P5.08x2.54mm_StaggerOdd_Lead3.8mm_Vertical -Package_TO_SOT_THT:TO-220-4_P5.08x2.54mm_StaggerOdd_Lead5.84mm_TabDown +Package_TO_SOT_THT:TO-220-4_P5.08x3.7mm_StaggerEven_Lead3.8mm_Vertical +Package_TO_SOT_THT:TO-220-4_P5.08x3.7mm_StaggerOdd_Lead3.8mm_Vertical +Package_TO_SOT_THT:TO-220-4_P5.08x3.8mm_StaggerEven_Lead5.85mm_TabDown +Package_TO_SOT_THT:TO-220-4_P5.08x3.8mm_StaggerOdd_Lead5.85mm_TabDown Package_TO_SOT_THT:TO-220-4_Vertical Package_TO_SOT_THT:TO-220-5_Horizontal_TabDown Package_TO_SOT_THT:TO-220-5_Horizontal_TabUp @@ -11487,6 +12214,8 @@ Package_TO_SOT_THT:TO-220-7_P2.54x3.7mm_StaggerEven_Lead3.8mm_Vertical Package_TO_SOT_THT:TO-220-7_P2.54x3.7mm_StaggerOdd_Lead3.8mm_Vertical Package_TO_SOT_THT:TO-220-7_P2.54x3.8mm_StaggerEven_Lead5.85mm_TabDown Package_TO_SOT_THT:TO-220-7_P2.54x3.8mm_StaggerOdd_Lead5.85mm_TabDown +Package_TO_SOT_THT:TO-220-7_P2.54x5.08mm_StaggerOdd_Lead3.08mm_Vertical +Package_TO_SOT_THT:TO-220-7_P2.54x5.1mm_StaggerOdd_Lead8.025mm_TabDown Package_TO_SOT_THT:TO-220-8_Vertical Package_TO_SOT_THT:TO-220-9_P1.94x3.7mm_StaggerEven_Lead3.8mm_Vertical Package_TO_SOT_THT:TO-220-9_P1.94x3.7mm_StaggerOdd_Lead3.8mm_Vertical @@ -11703,6 +12432,7 @@ Potentiometer_THT:Potentiometer_Alps_RK09L_Single_Vertical Potentiometer_THT:Potentiometer_Alps_RK09Y11_Single_Horizontal Potentiometer_THT:Potentiometer_Alps_RK163_Dual_Horizontal Potentiometer_THT:Potentiometer_Alps_RK163_Single_Horizontal +Potentiometer_THT:Potentiometer_Bourns_20P_Horizontal Potentiometer_THT:Potentiometer_Bourns_3005_Horizontal Potentiometer_THT:Potentiometer_Bourns_3006P_Horizontal Potentiometer_THT:Potentiometer_Bourns_3006W_Horizontal @@ -11732,7 +12462,7 @@ Potentiometer_THT:Potentiometer_Bourns_3339W_Horizontal Potentiometer_THT:Potentiometer_Bourns_3386C_Horizontal Potentiometer_THT:Potentiometer_Bourns_3386F_Vertical Potentiometer_THT:Potentiometer_Bourns_3386P_Vertical -Potentiometer_THT:Potentiometer_Bourns_3386W_Vertical +Potentiometer_THT:Potentiometer_Bourns_3386W_Horizontal Potentiometer_THT:Potentiometer_Bourns_3386X_Horizontal Potentiometer_THT:Potentiometer_Bourns_PTA1543_Single_Slide Potentiometer_THT:Potentiometer_Bourns_PTA2043_Single_Slide @@ -11741,6 +12471,7 @@ Potentiometer_THT:Potentiometer_Bourns_PTA4543_Single_Slide Potentiometer_THT:Potentiometer_Bourns_PTA6043_Single_Slide Potentiometer_THT:Potentiometer_Bourns_PTV09A-1_Single_Vertical Potentiometer_THT:Potentiometer_Bourns_PTV09A-2_Single_Horizontal +Potentiometer_THT:Potentiometer_Bourns_PTV112-4_Dual_Vertical Potentiometer_THT:Potentiometer_Omeg_PC16BU_Horizontal Potentiometer_THT:Potentiometer_Omeg_PC16BU_Vertical Potentiometer_THT:Potentiometer_Piher_PC-16_Dual_Horizontal @@ -11782,6 +12513,8 @@ Potentiometer_THT:Potentiometer_Vishay_T7-YA_Single_Vertical Potentiometer_THT:Potentiometer_Vishay_T73XW_Horizontal Potentiometer_THT:Potentiometer_Vishay_T73XX_Horizontal Potentiometer_THT:Potentiometer_Vishay_T73YP_Vertical +Potentiometer_THT:Potentiometer_Vishay_T93XA_Horizontal +Potentiometer_THT:Potentiometer_Vishay_T93YA_Vertical Relay_SMD:Relay_2P2T_10x6mm_TE_IMxxG Relay_SMD:Relay_DPDT_AXICOM_IMSeries_JLeg Relay_SMD:Relay_DPDT_FRT5_SMD @@ -11806,19 +12539,24 @@ Relay_SMD:Relay_SPDT_AXICOM_HF3Series_75ohms_Pitch1.27mm Relay_THT:Relay_1-Form-A_Schrack-RYII_RM5mm Relay_THT:Relay_1-Form-B_Schrack-RYII_RM5mm Relay_THT:Relay_1-Form-C_Schrack-RYII_RM3.2mm -Relay_THT:Relay_1P1T_NO_10x24x18.8mm_Panasonic_ADW11xxxxW_THT Relay_THT:Relay_3PST_COTO_3650 Relay_THT:Relay_3PST_COTO_3660 Relay_THT:Relay_DPDT_AXICOM_IMSeries_Pitch3.2mm Relay_THT:Relay_DPDT_AXICOM_IMSeries_Pitch5.08mm Relay_THT:Relay_DPDT_Finder_30.22 Relay_THT:Relay_DPDT_Finder_40.52 +Relay_THT:Relay_DPDT_Finder_40.62 Relay_THT:Relay_DPDT_FRT5 Relay_THT:Relay_DPDT_Fujitsu_FTR-F1C -Relay_THT:Relay_DPDT_Kemet_EC2 -Relay_THT:Relay_DPDT_Kemet_EC2_DoubleCoil +Relay_THT:Relay_DPDT_Hongfa_HF115F-2Z-x4 +Relay_THT:Relay_DPDT_Kemet_EC2_NJ +Relay_THT:Relay_DPDT_Kemet_EC2_NJ_DoubleCoil +Relay_THT:Relay_DPDT_Kemet_EC2_NU +Relay_THT:Relay_DPDT_Kemet_EC2_NU_DoubleCoil Relay_THT:Relay_DPDT_Omron_G2RL-2 Relay_THT:Relay_DPDT_Omron_G5V-2 +Relay_THT:Relay_DPDT_Omron_G6A +Relay_THT:Relay_DPDT_Omron_G6AK Relay_THT:Relay_DPDT_Omron_G6H-2 Relay_THT:Relay_DPDT_Omron_G6K-2P-Y Relay_THT:Relay_DPDT_Omron_G6K-2P @@ -11831,7 +12569,11 @@ Relay_THT:Relay_DPST_COTO_3602 Relay_THT:Relay_DPST_Fujitsu_FTR-F1A Relay_THT:Relay_DPST_Omron_G2RL-2A Relay_THT:Relay_DPST_Schrack-RT2-FormA_RM5mm +Relay_THT:Relay_NCR_HHG1D-1 +Relay_THT:Relay_Socket_3PDT_Omron_PLE11-0 +Relay_THT:Relay_Socket_4PDT_Omron_PY14-02 Relay_THT:Relay_Socket_DPDT_Finder_96.12 +Relay_THT:Relay_Socket_DPDT_Omron_PLE08-0 Relay_THT:Relay_SPDT_Finder_32.21-x000 Relay_THT:Relay_SPDT_Finder_34.51_Horizontal Relay_THT:Relay_SPDT_Finder_34.51_Vertical @@ -11840,8 +12582,13 @@ Relay_THT:Relay_SPDT_Finder_40.11 Relay_THT:Relay_SPDT_Finder_40.31 Relay_THT:Relay_SPDT_Finder_40.41 Relay_THT:Relay_SPDT_Finder_40.51 +Relay_THT:Relay_SPDT_Finder_40.61 Relay_THT:Relay_SPDT_Fujitsu_FTR-LYCA005x_FormC_Vertical Relay_THT:Relay_SPDT_HJR-4102 +Relay_THT:Relay_SPDT_Hongfa_HF3F-L-xx-1ZL1T +Relay_THT:Relay_SPDT_Hongfa_HF3F-L-xx-1ZL2T-R +Relay_THT:Relay_SPDT_Hongfa_HF3F-L-xx-1ZL2T +Relay_THT:Relay_SPDT_Hongfa_JQC-3FF_0XX-1Z Relay_THT:Relay_SPDT_HsinDa_Y14 Relay_THT:Relay_SPDT_Omron-G5LE-1 Relay_THT:Relay_SPDT_Omron-G5Q-1 @@ -11850,6 +12597,9 @@ Relay_THT:Relay_SPDT_Omron_G2RL-1 Relay_THT:Relay_SPDT_Omron_G5V-1 Relay_THT:Relay_SPDT_Omron_G6E Relay_THT:Relay_SPDT_Omron_G6EK +Relay_THT:Relay_SPDT_Panasonic_DR-L +Relay_THT:Relay_SPDT_Panasonic_DR-L2 +Relay_THT:Relay_SPDT_Panasonic_DR Relay_THT:Relay_SPDT_Panasonic_JW1_FormC Relay_THT:Relay_SPDT_PotterBrumfield_T9AP5D52_12V30A Relay_THT:Relay_SPDT_RAYEX-L90 @@ -11864,9 +12614,16 @@ Relay_THT:Relay_SPDT_Schrack-RT1-FormC_RM5mm Relay_THT:Relay_SPDT_StandexMeder_SIL_Form1C Relay_THT:Relay_SPST-NO_Fujitsu_FTR-LYAA005x_FormA_Vertical Relay_THT:Relay_SPST_Finder_32.21-x300 +Relay_THT:Relay_SPST_Hongfa_HF3F-L-xx-1HL1T +Relay_THT:Relay_SPST_Hongfa_HF3F-L-xx-1HL2T-R +Relay_THT:Relay_SPST_Hongfa_HF3F-L-xx-1HL2T +Relay_THT:Relay_SPST_Hongfa_JQC-3FF_0XX-1H Relay_THT:Relay_SPST_Omron-G5Q-1A Relay_THT:Relay_SPST_Omron_G2RL-1A-E Relay_THT:Relay_SPST_Omron_G2RL-1A +Relay_THT:Relay_SPST_Omron_G5NB +Relay_THT:Relay_SPST_Omron_G5PZ +Relay_THT:Relay_SPST_Panasonic_ADW11 Relay_THT:Relay_SPST_Panasonic_ALFG_FormA Relay_THT:Relay_SPST_Panasonic_ALFG_FormA_CircularHoles Relay_THT:Relay_SPST_Panasonic_JW1_FormA @@ -11890,9 +12647,11 @@ Relay_THT:Relay_SPST_StandexMeder_SIL_Form1A Relay_THT:Relay_SPST_StandexMeder_SIL_Form1B Relay_THT:Relay_SPST_TE_PCH-1xxx2M Relay_THT:Relay_SPST_TE_PCN-1xxD3MHZ +Relay_THT:Relay_SPST_Zettler-AZSR131 Relay_THT:Relay_StandexMeder_DIP_HighProfile Relay_THT:Relay_StandexMeder_DIP_LowProfile Relay_THT:Relay_StandexMeder_UMS +Relay_THT:Relay_Tyco_V23072_Sealed Resistor_SMD:R_01005_0402Metric Resistor_SMD:R_01005_0402Metric_Pad0.57x0.30mm_HandSolder Resistor_SMD:R_0201_0603Metric @@ -11945,6 +12704,7 @@ Resistor_SMD:R_Cat16-8 Resistor_SMD:R_MELF_MMB-0207 Resistor_SMD:R_MicroMELF_MMU-0102 Resistor_SMD:R_MiniMELF_MMA-0204 +Resistor_SMD:R_Shunt_Isabellenhuette_BVR4026 Resistor_SMD:R_Shunt_Ohmite_LVK12 Resistor_SMD:R_Shunt_Ohmite_LVK20 Resistor_SMD:R_Shunt_Ohmite_LVK24 @@ -12066,10 +12826,12 @@ RF_Antenna:Abracon_PRO-OB-440 RF_Antenna:Abracon_PRO-OB-471 RF_Antenna:Antenova_SR4G013_GPS RF_Antenna:Astrocast_AST50127-00 +RF_Antenna:AVX_M620720 RF_Antenna:Coilcraft_MA5532-AE_RFID RF_Antenna:Johanson_2450AT18x100 RF_Antenna:Johanson_2450AT43F0100 RF_Antenna:Molex_47948-0001_2.4Ghz +RF_Antenna:NiceRF_SW868-TH13_868Mhz RF_Antenna:Pulse_W3000 RF_Antenna:Pulse_W3011 RF_Antenna:Texas_SWRA117D_2.4GHz_Left @@ -12083,8 +12845,11 @@ RF_Converter:Balun_Johanson_1.6x0.8mm RF_Converter:Balun_Johanson_5400BL15B050E RF_Converter:RF_Attenuator_Susumu_PAT1220 RF_GPS:Linx_RXM-GPS +RF_GPS:OriginGPS_ORG1510 RF_GPS:Quectel_L70-R +RF_GPS:Quectel_L76 RF_GPS:Quectel_L80-R +RF_GPS:Quectel_L96 RF_GPS:Sierra_XA11X0 RF_GPS:Sierra_XM11X0 RF_GPS:SIM28ML @@ -12094,14 +12859,18 @@ RF_GPS:ublox_NEO RF_GPS:ublox_SAM-M8Q RF_GPS:ublox_SAM-M8Q_HandSolder RF_GPS:ublox_ZED +RF_GPS:ublox_ZOE_M8 RF_GSM:Quectel_BC66 RF_GSM:Quectel_BC95 +RF_GSM:Quectel_BG95 RF_GSM:Quectel_BG96 RF_GSM:Quectel_M95 RF_GSM:SIMCom_SIM800C RF_GSM:SIMCom_SIM900 +RF_GSM:Telit_SE150A4 RF_GSM:Telit_xL865 -RF_GSM:ublox_SARA-G3_LGA-96 +RF_GSM:ublox_LENA-R8_LGA-100 +RF_GSM:ublox_SARA_LGA-96 RF_Mini-Circuits:Mini-Circuits_BK377 RF_Mini-Circuits:Mini-Circuits_BK377_LandPatternPL-005 RF_Mini-Circuits:Mini-Circuits_CD541_H2.08mm @@ -12145,16 +12914,23 @@ RF_Module:Digi_XBee_SMT RF_Module:DWM1000 RF_Module:E18-MS1-PCB RF_Module:E73-2G4M04S +RF_Module:ESP-01 RF_Module:ESP-07 RF_Module:ESP-12E RF_Module:ESP-WROOM-02 +RF_Module:ESP32-C3-DevKitM-1 +RF_Module:ESP32-C3-WROOM-02 +RF_Module:ESP32-C3-WROOM-02U +RF_Module:ESP32-C6-MINI-1 RF_Module:ESP32-S2-MINI-1 RF_Module:ESP32-S2-MINI-1U RF_Module:ESP32-S2-WROVER RF_Module:ESP32-S3-WROOM-1 RF_Module:ESP32-S3-WROOM-1U +RF_Module:ESP32-S3-WROOM-2 RF_Module:ESP32-WROOM-32 RF_Module:ESP32-WROOM-32D +RF_Module:ESP32-WROOM-32E RF_Module:ESP32-WROOM-32U RF_Module:ESP32-WROOM-32UE RF_Module:Garmin_M8-35_9.8x14.0mm_Layout6x6_P1.5mm @@ -12164,7 +12940,9 @@ RF_Module:HOPERF_RFM9XW_SMD RF_Module:HOPERF_RFM9XW_THT RF_Module:IQRF_TRx2DA_KON-SIM-01 RF_Module:IQRF_TRx2D_KON-SIM-01 +RF_Module:Jadak_Thingmagic_M6e-Nano RF_Module:Laird_BL652 +RF_Module:MCU_Seeed_ESP32C3 RF_Module:Microchip_BM83 RF_Module:Microchip_RN4871 RF_Module:MOD-nRF8001 @@ -12173,17 +12951,22 @@ RF_Module:MonoWireless_TWE-L-WX RF_Module:NINA-B111 RF_Module:nRF24L01_Breakout RF_Module:Particle_P1 +RF_Module:RAK3172 RF_Module:RAK4200 RF_Module:RAK811 +RF_Module:Raytac_MDBT42Q RF_Module:Raytac_MDBT50Q RF_Module:RFDigital_RFD77101 RF_Module:RN2483 RF_Module:RN42 RF_Module:RN42N +RF_Module:ST-SiP-LGA-86-11x7.3mm RF_Module:ST_SPBTLE RF_Module:Taiyo-Yuden_EYSGJNZWY RF_Module:TD1205 RF_Module:TD1208 +RF_Module:WEMOS_C3_mini +RF_Module:WEMOS_D1_mini_light RF_Module:ZETA-433-SO_SMD RF_Module:ZETA-433-SO_THT RF_Shielding:Laird_Technologies_97-2002_25.40x25.40mm @@ -12219,6 +13002,7 @@ RF_Shielding:Wuerth_36503605_60x60mm RF_WiFi:USR-C322 Rotary_Encoder:RotaryEncoder_Alps_EC11E-Switch_Vertical_H20mm Rotary_Encoder:RotaryEncoder_Alps_EC11E-Switch_Vertical_H20mm_CircularMountingHoles +Rotary_Encoder:RotaryEncoder_Alps_EC11E-Switch_Vertical_H20mm_MountingHoles Rotary_Encoder:RotaryEncoder_Alps_EC11E_Vertical_H20mm Rotary_Encoder:RotaryEncoder_Alps_EC11E_Vertical_H20mm_CircularMountingHoles Rotary_Encoder:RotaryEncoder_Alps_EC12E-Switch_Vertical_H20mm @@ -12229,8 +13013,16 @@ Rotary_Encoder:RotaryEncoder_Bourns_Horizontal_PEC09-2xxxF-Nxxxx Rotary_Encoder:RotaryEncoder_Bourns_Horizontal_PEC09-2xxxF-Sxxxx Rotary_Encoder:RotaryEncoder_Bourns_Horizontal_PEC12R-2x17F-Nxxxx Rotary_Encoder:RotaryEncoder_Bourns_Horizontal_PEC12R-2x17F-Sxxxx +Rotary_Encoder:RotaryEncoder_Bourns_Horizontal_PEL12D-2x16F-Sxxxx +Rotary_Encoder:RotaryEncoder_Bourns_Horizontal_PEL12D-2x18F-Sxxxx +Rotary_Encoder:RotaryEncoder_Bourns_Horizontal_PEL12D-2x21F-Sxxxx +Rotary_Encoder:RotaryEncoder_Bourns_Horizontal_PEL12D-2x25S-Sxxxx +Rotary_Encoder:RotaryEncoder_Bourns_Horizontal_PEL12D-2x26F-Sxxxx +Rotary_Encoder:RotaryEncoder_Bourns_Horizontal_PEL12D-2x31F-Sxxxx Rotary_Encoder:RotaryEncoder_Bourns_Vertical_PEC12R-3x17F-Nxxxx Rotary_Encoder:RotaryEncoder_Bourns_Vertical_PEC12R-3x17F-Sxxxx +Rotary_Encoder:RotaryEncoder_Bourns_Vertical_PEL12D-4x25S-Sxxxx +Rotary_Encoder:RotaryEncoder_Bourns_Vertical_PEL12D-4xxxF-Sxxxx Sensor:Aosong_DHT11_5.5x12.0_P2.54mm Sensor:ASAIR_AM2302_P2.54mm_Lead2.75mm_TabDown Sensor:ASAIR_AM2302_P2.54mm_Vertical @@ -12244,6 +13036,7 @@ Sensor:Sensirion_SCD4x-1EP_10.1x10.1mm_P1.25mm_EP4.8x4.8mm Sensor:Sensortech_MiCS_5x7mm_P1.25mm Sensor:SHT1x Sensor:SPEC_110-xxx_SMD-10Pin_20x20mm_P4.0mm +Sensor:TGS-5141 Sensor:Winson_GM-402B_5x5mm_P1.27mm Sensor_Audio:CUI_CMC-4013-SMT Sensor_Audio:Infineon_PG-LLGA-5-1 @@ -12252,6 +13045,7 @@ Sensor_Audio:InvenSense_ICS-43434-6_3.5x2.65mm Sensor_Audio:Knowles_LGA-5_3.5x2.65mm Sensor_Audio:Knowles_LGA-6_4.72x3.76mm Sensor_Audio:Knowles_SPH0645LM4H-6_3.5x2.65mm +Sensor_Audio:POM-2244P-C3310-2-R Sensor_Audio:ST_HLGA-6_3.76x4.72mm_P1.65mm Sensor_Current:AKM_CQ_7 Sensor_Current:AKM_CQ_7S @@ -12286,8 +13080,10 @@ Sensor_Current:LEM_HX15-P-SP2 Sensor_Current:LEM_HX20-P-SP2 Sensor_Current:LEM_HX25-P-SP2 Sensor_Current:LEM_HX50-P-SP2 +Sensor_Current:LEM_LA25-NP Sensor_Current:LEM_LA25-P Sensor_Current:LEM_LTSR-NP +Sensor_Distance:AMS_OLGA12 Sensor_Distance:ST_VL53L1x Sensor_Humidity:Sensirion_DFN-4-1EP_2x2mm_P1mm_EP0.7x1.6mm Sensor_Humidity:Sensirion_DFN-4_1.5x1.5mm_P0.8mm_SHT4x_NoCentralPad @@ -12297,6 +13093,7 @@ Sensor_Motion:InvenSense_QFN-24_3x3mm_P0.4mm Sensor_Motion:InvenSense_QFN-24_3x3mm_P0.4mm_NoMask Sensor_Motion:InvenSense_QFN-24_4x4mm_P0.5mm Sensor_Motion:InvenSense_QFN-24_4x4mm_P0.5mm_NoMask +Sensor_Pressure:CFSensor_XGZP6859D_7x7mm Sensor_Pressure:CFSensor_XGZP6897x Sensor_Pressure:CFSensor_XGZP6899x Sensor_Pressure:Freescale_98ARH99066A @@ -12421,8 +13218,9 @@ Symbol:RoHS-Logo_30mm_SilkScreen Symbol:RoHS-Logo_40mm_SilkScreen Symbol:RoHS-Logo_6mm_SilkScreen Symbol:RoHS-Logo_8mm_SilkScreen -Symbol:Symbol_Attention_CopperTop_Big -Symbol:Symbol_Attention_CopperTop_Small +Symbol:Smolhaj_Scale_0.1 +Symbol:Symbol_Attention_Triangle_17x15mm_Copper +Symbol:Symbol_Attention_Triangle_8x7mm_Copper Symbol:Symbol_Barrel_Polarity Symbol:Symbol_CC-Attribution_CopperTop_Big Symbol:Symbol_CC-Attribution_CopperTop_Small @@ -12439,19 +13237,19 @@ Symbol:Symbol_CreativeCommons_CopperTop_Type1_Big Symbol:Symbol_CreativeCommons_CopperTop_Type2_Big Symbol:Symbol_CreativeCommons_CopperTop_Type2_Small Symbol:Symbol_CreativeCommons_SilkScreenTop_Type2_Big -Symbol:Symbol_Danger_CopperTop_Big -Symbol:Symbol_Danger_CopperTop_Small +Symbol:Symbol_Danger_18x16mm_Copper +Symbol:Symbol_Danger_8x8mm_Copper Symbol:Symbol_ESD-Logo-Text_CopperTop Symbol:Symbol_ESD-Logo_CopperTop Symbol:Symbol_GNU-GPL_CopperTop_Big Symbol:Symbol_GNU-GPL_CopperTop_Small Symbol:Symbol_GNU-Logo_CopperTop Symbol:Symbol_GNU-Logo_SilkscreenTop -Symbol:Symbol_HighVoltage_Type1_CopperTop_Big -Symbol:Symbol_Highvoltage_Type1_CopperTop_Small -Symbol:Symbol_HighVoltage_Type2_CopperTop_Big -Symbol:Symbol_Highvoltage_Type2_CopperTop_Small -Symbol:Symbol_HighVoltage_Type2_CopperTop_VerySmall +Symbol:Symbol_HighVoltage_NoTriangle_2x5mm_Copper +Symbol:Symbol_HighVoltage_NoTriangle_6x15mm_Copper +Symbol:Symbol_HighVoltage_Triangle_17x15mm_Copper +Symbol:Symbol_HighVoltage_Triangle_6x6mm_Copper +Symbol:Symbol_HighVoltage_Triangle_8x7mm_Copper Symbol:UKCA-Logo_12x12mm_SilkScreen Symbol:UKCA-Logo_20x20mm_SilkScreen Symbol:UKCA-Logo_30x30mm_SilkScreen @@ -12472,7 +13270,54 @@ TerminalBlock:TerminalBlock_bornier-3_P5.08mm TerminalBlock:TerminalBlock_bornier-4_P5.08mm TerminalBlock:TerminalBlock_bornier-5_P5.08mm TerminalBlock:TerminalBlock_bornier-6_P5.08mm +TerminalBlock:TerminalBlock_bornier-8_P5.08mm +TerminalBlock:TerminalBlock_Degson_DG246-3.81-03P +TerminalBlock:TerminalBlock_MaiXu_MX126-5.0-02P_1x02_P5.00mm +TerminalBlock:TerminalBlock_MaiXu_MX126-5.0-03P_1x03_P5.00mm +TerminalBlock:TerminalBlock_MaiXu_MX126-5.0-04P_1x04_P5.00mm +TerminalBlock:TerminalBlock_MaiXu_MX126-5.0-05P_1x05_P5.00mm +TerminalBlock:TerminalBlock_MaiXu_MX126-5.0-06P_1x06_P5.00mm +TerminalBlock:TerminalBlock_MaiXu_MX126-5.0-07P_1x07_P5.00mm +TerminalBlock:TerminalBlock_MaiXu_MX126-5.0-08P_1x08_P5.00mm +TerminalBlock:TerminalBlock_MaiXu_MX126-5.0-09P_1x09_P5.00mm +TerminalBlock:TerminalBlock_MaiXu_MX126-5.0-10P_1x10_P5.00mm +TerminalBlock:TerminalBlock_MaiXu_MX126-5.0-11P_1x11_P5.00mm +TerminalBlock:TerminalBlock_MaiXu_MX126-5.0-12P_1x12_P5.00mm +TerminalBlock:TerminalBlock_MaiXu_MX126-5.0-13P_1x13_P5.00mm +TerminalBlock:TerminalBlock_MaiXu_MX126-5.0-14P_1x14_P5.00mm +TerminalBlock:TerminalBlock_MaiXu_MX126-5.0-15P_1x15_P5.00mm +TerminalBlock:TerminalBlock_MaiXu_MX126-5.0-16P_1x16_P5.00mm +TerminalBlock:TerminalBlock_MaiXu_MX126-5.0-17P_1x17_P5.00mm +TerminalBlock:TerminalBlock_MaiXu_MX126-5.0-18P_1x18_P5.00mm +TerminalBlock:TerminalBlock_MaiXu_MX126-5.0-19P_1x19_P5.00mm +TerminalBlock:TerminalBlock_MaiXu_MX126-5.0-20P_1x20_P5.00mm +TerminalBlock:TerminalBlock_MaiXu_MX126-5.0-21P_1x21_P5.00mm +TerminalBlock:TerminalBlock_MaiXu_MX126-5.0-22P_1x22_P5.00mm +TerminalBlock:TerminalBlock_MaiXu_MX126-5.0-23P_1x23_P5.00mm +TerminalBlock:TerminalBlock_MaiXu_MX126-5.0-24P_1x24_P5.00mm TerminalBlock:TerminalBlock_Wuerth_691311400102_P7.62mm +TerminalBlock:TerminalBlock_Xinya_XY308-2.54-10P_1x10_P2.54mm_Horizontal +TerminalBlock:TerminalBlock_Xinya_XY308-2.54-11P_1x11_P2.54mm_Horizontal +TerminalBlock:TerminalBlock_Xinya_XY308-2.54-12P_1x12_P2.54mm_Horizontal +TerminalBlock:TerminalBlock_Xinya_XY308-2.54-13P_1x13_P2.54mm_Horizontal +TerminalBlock:TerminalBlock_Xinya_XY308-2.54-14P_1x14_P2.54mm_Horizontal +TerminalBlock:TerminalBlock_Xinya_XY308-2.54-15P_1x15_P2.54mm_Horizontal +TerminalBlock:TerminalBlock_Xinya_XY308-2.54-16P_1x16_P2.54mm_Horizontal +TerminalBlock:TerminalBlock_Xinya_XY308-2.54-17P_1x17_P2.54mm_Horizontal +TerminalBlock:TerminalBlock_Xinya_XY308-2.54-18P_1x18_P2.54mm_Horizontal +TerminalBlock:TerminalBlock_Xinya_XY308-2.54-19P_1x19_P2.54mm_Horizontal +TerminalBlock:TerminalBlock_Xinya_XY308-2.54-20P_1x20_P2.54mm_Horizontal +TerminalBlock:TerminalBlock_Xinya_XY308-2.54-21P_1x21_P2.54mm_Horizontal +TerminalBlock:TerminalBlock_Xinya_XY308-2.54-22P_1x22_P2.54mm_Horizontal +TerminalBlock:TerminalBlock_Xinya_XY308-2.54-23P_1x23_P2.54mm_Horizontal +TerminalBlock:TerminalBlock_Xinya_XY308-2.54-2P_1x02_P2.54mm_Horizontal +TerminalBlock:TerminalBlock_Xinya_XY308-2.54-3P_1x03_P2.54mm_Horizontal +TerminalBlock:TerminalBlock_Xinya_XY308-2.54-4P_1x04_P2.54mm_Horizontal +TerminalBlock:TerminalBlock_Xinya_XY308-2.54-5P_1x05_P2.54mm_Horizontal +TerminalBlock:TerminalBlock_Xinya_XY308-2.54-6P_1x06_P2.54mm_Horizontal +TerminalBlock:TerminalBlock_Xinya_XY308-2.54-7P_1x07_P2.54mm_Horizontal +TerminalBlock:TerminalBlock_Xinya_XY308-2.54-8P_1x08_P2.54mm_Horizontal +TerminalBlock:TerminalBlock_Xinya_XY308-2.54-9P_1x09_P2.54mm_Horizontal TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x02_P3.50mm_Horizontal TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x02_P3.50mm_Vertical TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x03_P3.50mm_Horizontal @@ -12501,6 +13346,29 @@ TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x14_P3.50mm_Horizontal TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x14_P3.50mm_Vertical TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x15_P3.50mm_Horizontal TerminalBlock_4Ucon:TerminalBlock_4Ucon_1x15_P3.50mm_Vertical +TerminalBlock_Altech:Altech_AK100_1x02_P5.00mm +TerminalBlock_Altech:Altech_AK100_1x03_P5.00mm +TerminalBlock_Altech:Altech_AK100_1x04_P5.00mm +TerminalBlock_Altech:Altech_AK100_1x05_P5.00mm +TerminalBlock_Altech:Altech_AK100_1x06_P5.00mm +TerminalBlock_Altech:Altech_AK100_1x07_P5.00mm +TerminalBlock_Altech:Altech_AK100_1x08_P5.00mm +TerminalBlock_Altech:Altech_AK100_1x09_P5.00mm +TerminalBlock_Altech:Altech_AK100_1x10_P5.00mm +TerminalBlock_Altech:Altech_AK100_1x11_P5.00mm +TerminalBlock_Altech:Altech_AK100_1x12_P5.00mm +TerminalBlock_Altech:Altech_AK100_1x13_P5.00mm +TerminalBlock_Altech:Altech_AK100_1x14_P5.00mm +TerminalBlock_Altech:Altech_AK100_1x15_P5.00mm +TerminalBlock_Altech:Altech_AK100_1x16_P5.00mm +TerminalBlock_Altech:Altech_AK100_1x17_P5.00mm +TerminalBlock_Altech:Altech_AK100_1x18_P5.00mm +TerminalBlock_Altech:Altech_AK100_1x19_P5.00mm +TerminalBlock_Altech:Altech_AK100_1x20_P5.00mm +TerminalBlock_Altech:Altech_AK100_1x21_P5.00mm +TerminalBlock_Altech:Altech_AK100_1x22_P5.00mm +TerminalBlock_Altech:Altech_AK100_1x23_P5.00mm +TerminalBlock_Altech:Altech_AK100_1x24_P5.00mm TerminalBlock_Altech:Altech_AK300_1x02_P5.00mm_45-Degree TerminalBlock_Altech:Altech_AK300_1x03_P5.00mm_45-Degree TerminalBlock_Altech:Altech_AK300_1x04_P5.00mm_45-Degree @@ -12524,6 +13392,29 @@ TerminalBlock_Altech:Altech_AK300_1x21_P5.00mm_45-Degree TerminalBlock_Altech:Altech_AK300_1x22_P5.00mm_45-Degree TerminalBlock_Altech:Altech_AK300_1x23_P5.00mm_45-Degree TerminalBlock_Altech:Altech_AK300_1x24_P5.00mm_45-Degree +TerminalBlock_CUI:TerminalBlock_CUI_TB007-508-02_1x02_P5.08mm_Horizontal +TerminalBlock_CUI:TerminalBlock_CUI_TB007-508-03_1x03_P5.08mm_Horizontal +TerminalBlock_CUI:TerminalBlock_CUI_TB007-508-04_1x04_P5.08mm_Horizontal +TerminalBlock_CUI:TerminalBlock_CUI_TB007-508-05_1x05_P5.08mm_Horizontal +TerminalBlock_CUI:TerminalBlock_CUI_TB007-508-06_1x06_P5.08mm_Horizontal +TerminalBlock_CUI:TerminalBlock_CUI_TB007-508-07_1x07_P5.08mm_Horizontal +TerminalBlock_CUI:TerminalBlock_CUI_TB007-508-08_1x08_P5.08mm_Horizontal +TerminalBlock_CUI:TerminalBlock_CUI_TB007-508-09_1x09_P5.08mm_Horizontal +TerminalBlock_CUI:TerminalBlock_CUI_TB007-508-10_1x10_P5.08mm_Horizontal +TerminalBlock_CUI:TerminalBlock_CUI_TB007-508-11_1x11_P5.08mm_Horizontal +TerminalBlock_CUI:TerminalBlock_CUI_TB007-508-12_1x12_P5.08mm_Horizontal +TerminalBlock_CUI:TerminalBlock_CUI_TB007-508-13_1x13_P5.08mm_Horizontal +TerminalBlock_CUI:TerminalBlock_CUI_TB007-508-14_1x14_P5.08mm_Horizontal +TerminalBlock_CUI:TerminalBlock_CUI_TB007-508-15_1x15_P5.08mm_Horizontal +TerminalBlock_CUI:TerminalBlock_CUI_TB007-508-16_1x16_P5.08mm_Horizontal +TerminalBlock_CUI:TerminalBlock_CUI_TB007-508-17_1x17_P5.08mm_Horizontal +TerminalBlock_CUI:TerminalBlock_CUI_TB007-508-18_1x18_P5.08mm_Horizontal +TerminalBlock_CUI:TerminalBlock_CUI_TB007-508-19_1x19_P5.08mm_Horizontal +TerminalBlock_CUI:TerminalBlock_CUI_TB007-508-20_1x20_P5.08mm_Horizontal +TerminalBlock_CUI:TerminalBlock_CUI_TB007-508-21_1x21_P5.08mm_Horizontal +TerminalBlock_CUI:TerminalBlock_CUI_TB007-508-22_1x22_P5.08mm_Horizontal +TerminalBlock_CUI:TerminalBlock_CUI_TB007-508-23_1x23_P5.08mm_Horizontal +TerminalBlock_CUI:TerminalBlock_CUI_TB007-508-24_1x24_P5.08mm_Horizontal TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-02_P10.00mm TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-03_P10.00mm TerminalBlock_Dinkle:TerminalBlock_Dinkle_DT-55-B01X-04_P10.00mm @@ -12817,7 +13708,9 @@ TerminalBlock_RND:TerminalBlock_RND_205-00238_1x08_P5.08mm_Horizontal TerminalBlock_RND:TerminalBlock_RND_205-00239_1x09_P5.08mm_Horizontal TerminalBlock_RND:TerminalBlock_RND_205-00240_1x10_P5.08mm_Horizontal TerminalBlock_RND:TerminalBlock_RND_205-00241_1x02_P10.16mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00241_1x11_P5.08mm_Horizontal TerminalBlock_RND:TerminalBlock_RND_205-00242_1x03_P10.16mm_Horizontal +TerminalBlock_RND:TerminalBlock_RND_205-00242_1x12_P5.08mm_Horizontal TerminalBlock_RND:TerminalBlock_RND_205-00243_1x04_P10.16mm_Horizontal TerminalBlock_RND:TerminalBlock_RND_205-00244_1x05_P10.16mm_Horizontal TerminalBlock_RND:TerminalBlock_RND_205-00245_1x06_P10.16mm_Horizontal @@ -12871,6 +13764,16 @@ TerminalBlock_TE-Connectivity:TerminalBlock_TE_282834-6_1x06_P2.54mm_Horizontal TerminalBlock_TE-Connectivity:TerminalBlock_TE_282834-7_1x07_P2.54mm_Horizontal TerminalBlock_TE-Connectivity:TerminalBlock_TE_282834-8_1x08_P2.54mm_Horizontal TerminalBlock_TE-Connectivity:TerminalBlock_TE_282834-9_1x09_P2.54mm_Horizontal +TerminalBlock_WAGO:TerminalBlock_WAGO_233-502_2x02_P2.54mm +TerminalBlock_WAGO:TerminalBlock_WAGO_233-503_2x03_P2.54mm +TerminalBlock_WAGO:TerminalBlock_WAGO_233-504_2x04_P2.54mm +TerminalBlock_WAGO:TerminalBlock_WAGO_233-505_2x05_P2.54mm +TerminalBlock_WAGO:TerminalBlock_WAGO_233-506_2x06_P2.54mm +TerminalBlock_WAGO:TerminalBlock_WAGO_233-507_2x07_P2.54mm +TerminalBlock_WAGO:TerminalBlock_WAGO_233-508_2x08_P2.54mm +TerminalBlock_WAGO:TerminalBlock_WAGO_233-509_2x09_P2.54mm +TerminalBlock_WAGO:TerminalBlock_WAGO_233-510_2x10_P2.54mm +TerminalBlock_WAGO:TerminalBlock_WAGO_233-512_2x12_P2.54mm TerminalBlock_WAGO:TerminalBlock_WAGO_236-101_1x01_P5.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-102_1x02_P5.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-103_1x03_P5.00mm_45Degree @@ -12880,8 +13783,12 @@ TerminalBlock_WAGO:TerminalBlock_WAGO_236-106_1x06_P5.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-107_1x07_P5.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-108_1x08_P5.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-109_1x09_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-110_1x10_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-111_1x11_P5.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-112_1x12_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-113_1x13_P5.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-114_1x14_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-115_1x15_P5.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-116_1x16_P5.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-124_1x24_P5.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-136_1x36_P5.00mm_45Degree @@ -12895,7 +13802,12 @@ TerminalBlock_WAGO:TerminalBlock_WAGO_236-206_1x06_P7.50mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-207_1x07_P7.50mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-208_1x08_P7.50mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-209_1x09_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-210_1x10_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-211_1x11_P7.50mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-212_1x12_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-213_1x13_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-214_1x14_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-215_1x15_P7.50mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-216_1x16_P7.50mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-224_1x24_P7.50mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-301_1x01_P10.00mm_45Degree @@ -12904,9 +13816,15 @@ TerminalBlock_WAGO:TerminalBlock_WAGO_236-303_1x03_P10.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-304_1x04_P10.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-305_1x05_P10.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-306_1x06_P10.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-307_1x07_P10.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-308_1x08_P10.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-309_1x09_P10.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-310_1x10_P10.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-311_1x11_P10.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-312_1x12_P10.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-313_1x13_P10.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-314_1x14_P10.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-315_1x15_P10.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-316_1x16_P10.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-324_1x24_P10.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-401_1x01_P5.00mm_45Degree @@ -12918,8 +13836,12 @@ TerminalBlock_WAGO:TerminalBlock_WAGO_236-406_1x06_P5.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-407_1x07_P5.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-408_1x08_P5.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-409_1x09_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-410_1x10_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-411_1x11_P5.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-412_1x12_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-413_1x13_P5.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-414_1x14_P5.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-415_1x15_P5.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-416_1x16_P5.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-424_1x24_P5.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-436_1x36_P5.00mm_45Degree @@ -12933,7 +13855,12 @@ TerminalBlock_WAGO:TerminalBlock_WAGO_236-506_1x06_P7.50mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-507_1x07_P7.50mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-508_1x08_P7.50mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-509_1x09_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-510_1x10_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-511_1x11_P7.50mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-512_1x12_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-513_1x13_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-514_1x14_P7.50mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-515_1x15_P7.50mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-516_1x16_P7.50mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-524_1x24_P7.50mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-601_1x01_P10.00mm_45Degree @@ -12942,9 +13869,15 @@ TerminalBlock_WAGO:TerminalBlock_WAGO_236-603_1x03_P10.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-604_1x04_P10.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-605_1x05_P10.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-606_1x06_P10.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-607_1x07_P10.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-608_1x08_P10.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-609_1x09_P10.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-610_1x10_P10.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-611_1x11_P10.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-612_1x12_P10.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-613_1x13_P10.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-614_1x14_P10.00mm_45Degree +TerminalBlock_WAGO:TerminalBlock_WAGO_236-615_1x15_P10.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-616_1x16_P10.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_236-624_1x24_P10.00mm_45Degree TerminalBlock_WAGO:TerminalBlock_WAGO_804-101_1x01_P5.00mm_45Degree @@ -13005,8 +13938,8 @@ TestPoint:TestPoint_Bridge_Pitch7.62mm_Drill1.3mm TestPoint:TestPoint_Keystone_5000-5004_Miniature TestPoint:TestPoint_Keystone_5005-5009_Compact TestPoint:TestPoint_Keystone_5010-5014_Multipurpose -TestPoint:TestPoint_Keystone_5015_Micro-Minature -TestPoint:TestPoint_Keystone_5019_Minature +TestPoint:TestPoint_Keystone_5015_Micro_Mini +TestPoint:TestPoint_Keystone_5019_Miniature TestPoint:TestPoint_Loop_D1.80mm_Drill1.0mm_Beaded TestPoint:TestPoint_Loop_D2.50mm_Drill1.0mm TestPoint:TestPoint_Loop_D2.50mm_Drill1.0mm_LowProfile @@ -13062,6 +13995,7 @@ Transformer_SMD:Transformer_Coilcraft_CST1 Transformer_SMD:Transformer_Coilcraft_CST2 Transformer_SMD:Transformer_Coilcraft_CST2010 Transformer_SMD:Transformer_CurrentSense_8.4x7.2mm +Transformer_SMD:Transformer_ED8_4-Lead_10.5x8mm_P5mm Transformer_SMD:Transformer_Ethernet_Bel_S558-5999-T7-F Transformer_SMD:Transformer_Ethernet_Bourns_PT61017PEL Transformer_SMD:Transformer_Ethernet_Bourns_PT61020EL @@ -13143,12 +14077,19 @@ Transformer_THT:Transformer_Toroid_Tapped_Horizontal_D10.5mm_Amidon-T37 Transformer_THT:Transformer_Toroid_Tapped_Horizontal_D12.5mm_Amidon-T44 Transformer_THT:Transformer_Toroid_Tapped_Horizontal_D14.0mm_Amidon-T50 Transformer_THT:Transformer_Toroid_Tapped_Horizontal_D9.0mm_Amidon-T30 +Transformer_THT:Transformer_Triad_VPP16-310 Transformer_THT:Transformer_Wuerth_750343373 Transformer_THT:Transformer_Wuerth_760871131 +Transformer_THT:Transformer_Zeming_ZMCT103C +Transformer_THT:Transformer_Zeming_ZMPT101K Transistor_Power:GaN_Systems_GaNPX-3_5x6.6mm_Drain2.93x0.6mm Transistor_Power:GaN_Systems_GaNPX-3_5x6.6mm_Drain3.76x0.6mm Transistor_Power:GaN_Systems_GaNPX-4_7x8.4mm Transistor_Power_Module:Infineon_AG-ECONO2 +Transistor_Power_Module:Infineon_AG-ECONO3 +Transistor_Power_Module:Infineon_AG-ECONO3B +Transistor_Power_Module:Infineon_EasyPACK-1B +Transistor_Power_Module:Infineon_EasyPACK-1B_PressFIT Transistor_Power_Module:Infineon_EasyPIM-1B Transistor_Power_Module:Infineon_EasyPIM-2B Transistor_Power_Module:Littelfuse_Package_H_XBN2MM diff --git a/public/kicad/symbols.txt b/public/kicad/symbols.txt index 4a0999b74..1cee16927 100644 --- a/public/kicad/symbols.txt +++ b/public/kicad/symbols.txt @@ -1,6 +1,6 @@ # This file contains all the KiCad symbols available in the official library # Generated by symbols.sh -# on Sun Dec 3 22:09:24 CET 2023 +# on Sun Feb 16 21:42:01 CET 2025 4xxx:14528 4xxx:14529 4xxx:14538 @@ -31,6 +31,7 @@ 4xxx:4052 4xxx:4053 4xxx:4056 +4xxx:4060 4xxx:4066 4xxx:4069 4xxx:4070 @@ -48,7 +49,9 @@ 4xxx:4528 4xxx:4538 4xxx:4543 +4xxx:CD4033B 4xxx:HEF4093B +4xxx:HEF4094B 4xxx_IEEE:4001 4xxx_IEEE:4002 4xxx_IEEE:4006 @@ -103,6 +106,7 @@ 4xxx_IEEE:4051 4xxx_IEEE:4052 4xxx_IEEE:4053 +4xxx_IEEE:4060 4xxx_IEEE:4066 4xxx_IEEE:4068 4xxx_IEEE:4069 @@ -307,29 +311,56 @@ 74xGxx:74LVC3G17 74xGxx:74LVC3G34 74xGxx:74LVC3GU04 +74xGxx:Inverter_Schmitt_Dual 74xGxx:NC7SVU04P5X 74xGxx:NC7SZ125M5X 74xGxx:NC7SZ125P5X +74xGxx:SN74LVC1G00DBV +74xGxx:SN74LVC1G00DCK +74xGxx:SN74LVC1G00DRL +74xGxx:SN74LVC1G125DBV +74xGxx:SN74LVC1G125DCK +74xGxx:SN74LVC1G125DRL 74xGxx:SN74LVC1G14DBV 74xGxx:SN74LVC1G14DRL +74xGxx:SN74LVC2G14DBV +74xGxx:TC7PZ14FU 74xx:7400 74xx:7402 74xx:74469 +74xx:7454 74xx:74AHC04 74xx:74AHC240 74xx:74AHC244 74xx:74AHC273 74xx:74AHC373 74xx:74AHC374 +74xx:74AHC541 74xx:74AHC595 74xx:74AHCT04 74xx:74AHCT123 +74xx:74AHCT125 74xx:74AHCT240 74xx:74AHCT244 74xx:74AHCT273 74xx:74AHCT373 74xx:74AHCT374 +74xx:74AHCT541 74xx:74AHCT595 +74xx:74ALVC164245 +74xx:74CB3Q16210DGG +74xx:74CB3Q16210DGV +74xx:74CB3Q16210DL +74xx:74CB3T16210DGG +74xx:74CB3T16210DGV +74xx:74CBT16210CDGG +74xx:74CBT16210CDGV +74xx:74CBT16210CDL +74xx:74CBT3861 +74xx:74CBTD16210DGG +74xx:74CBTD16210DGV +74xx:74CBTD16210DL +74xx:74CBTD3861 74xx:74CBTLV16212 74xx:74CBTLV3257 74xx:74CBTLV3861 @@ -342,6 +373,9 @@ 74xx:74HC14 74xx:74HC164 74xx:74HC165 +74xx:74HC173 +74xx:74HC192 +74xx:74HC193 74xx:74HC237 74xx:74HC238 74xx:74HC240 @@ -352,13 +386,16 @@ 74xx:74HC374 74xx:74HC4024 74xx:74HC4051 +74xx:74HC4060 74xx:74HC590 74xx:74HC590A +74xx:74HC594 74xx:74HC595 74xx:74HC596 74xx:74HC688 74xx:74HC7014 74xx:74HC74 +74xx:74HC85 74xx:74HC86 74xx:74HCT00 74xx:74HCT02 @@ -367,6 +404,8 @@ 74xx:74HCT137 74xx:74HCT138 74xx:74HCT164 +74xx:74HCT173 +74xx:74HCT193 74xx:74HCT237 74xx:74HCT238 74xx:74HCT240 @@ -380,6 +419,8 @@ 74xx:74HCT595 74xx:74HCT596 74xx:74HCT688 +74xx:74HCT74 +74xx:74HCT85 74xx:74LCX07 74xx:74LS00 74xx:74LS01 @@ -472,6 +513,7 @@ 74xx:74LS26 74xx:74LS27 74xx:74LS273 +74xx:74LS279 74xx:74LS28 74xx:74LS280 74xx:74LS283 @@ -514,9 +556,9 @@ 74xx:74LS48 74xx:74LS49 74xx:74LS51 -74xx:74LS54 74xx:74LS540 74xx:74LS541 +74xx:74LS54N 74xx:74LS55 74xx:74LS573 74xx:74LS574 @@ -549,6 +591,8 @@ 74xx:CD74HC4067SM 74xx:MC74LCX16245DT 74xx:MM74C923 +74xx:SN74ALVC164245DGG +74xx:SN74ALVC164245DL 74xx:SN74AVC16827DGGR 74xx:SN74CB3Q3384ADBQ 74xx:SN74CB3Q3384APW @@ -782,8 +826,10 @@ Amplifier_Audio:MA12070 Amplifier_Audio:MA12070P Amplifier_Audio:MAX9701xTG Amplifier_Audio:MAX9715xTE+ +Amplifier_Audio:MAX9744 Amplifier_Audio:MAX9814 Amplifier_Audio:MAX98306xDT +Amplifier_Audio:MAX98396EWB+ Amplifier_Audio:MAX9850xTI Amplifier_Audio:OPA1622 Amplifier_Audio:PAM8301 @@ -791,6 +837,7 @@ Amplifier_Audio:PAM8302AAD Amplifier_Audio:PAM8302AAS Amplifier_Audio:PAM8302AAY Amplifier_Audio:PAM8403D +Amplifier_Audio:PAM8406D Amplifier_Audio:SSM2017P Amplifier_Audio:SSM2018 Amplifier_Audio:SSM2120 @@ -839,8 +886,14 @@ Amplifier_Audio:THAT2181 Amplifier_Audio:TPA3251 Amplifier_Audio:TPA6110A2DGN Amplifier_Audio:TPA6132A2RTE +Amplifier_Audio:TPA6203A1DGN +Amplifier_Audio:TPA6203A1DRB Amplifier_Buffer:BUF602xD Amplifier_Buffer:BUF602xDBV +Amplifier_Buffer:BUF634AxD +Amplifier_Buffer:BUF634AxDDA +Amplifier_Buffer:BUF634AxDRB +Amplifier_Buffer:BUF634U Amplifier_Buffer:EL2001CN Amplifier_Buffer:LH0002H Amplifier_Buffer:LM6321H @@ -873,7 +926,12 @@ Amplifier_Current:INA180A1 Amplifier_Current:INA180A2 Amplifier_Current:INA180A3 Amplifier_Current:INA180A4 +Amplifier_Current:INA180B1 +Amplifier_Current:INA180B2 +Amplifier_Current:INA180B3 +Amplifier_Current:INA180B4 Amplifier_Current:INA181 +Amplifier_Current:INA185 Amplifier_Current:INA193 Amplifier_Current:INA194 Amplifier_Current:INA195 @@ -881,6 +939,12 @@ Amplifier_Current:INA196 Amplifier_Current:INA197 Amplifier_Current:INA198 Amplifier_Current:INA199xxDCK +Amplifier_Current:INA200D +Amplifier_Current:INA200DGK +Amplifier_Current:INA201D +Amplifier_Current:INA201DGK +Amplifier_Current:INA202D +Amplifier_Current:INA202DGK Amplifier_Current:INA225 Amplifier_Current:INA240A1D Amplifier_Current:INA240A1PW @@ -890,12 +954,61 @@ Amplifier_Current:INA240A3D Amplifier_Current:INA240A3PW Amplifier_Current:INA240A4D Amplifier_Current:INA240A4PW +Amplifier_Current:INA241A1xD +Amplifier_Current:INA241A1xDDF +Amplifier_Current:INA241A1xDGK +Amplifier_Current:INA241A2xD +Amplifier_Current:INA241A2xDDF +Amplifier_Current:INA241A2xDGK +Amplifier_Current:INA241A3xD +Amplifier_Current:INA241A3xDDF +Amplifier_Current:INA241A3xDGK +Amplifier_Current:INA241A4xD +Amplifier_Current:INA241A4xDDF +Amplifier_Current:INA241A4xDGK +Amplifier_Current:INA241A5xD +Amplifier_Current:INA241A5xDDF +Amplifier_Current:INA241A5xDGK +Amplifier_Current:INA241B1xD +Amplifier_Current:INA241B1xDDF +Amplifier_Current:INA241B1xDGK +Amplifier_Current:INA241B2xD +Amplifier_Current:INA241B2xDDF +Amplifier_Current:INA241B2xDGK +Amplifier_Current:INA241B3xD +Amplifier_Current:INA241B3xDDF +Amplifier_Current:INA241B3xDGK +Amplifier_Current:INA241B4xD +Amplifier_Current:INA241B4xDDF +Amplifier_Current:INA241B4xDGK +Amplifier_Current:INA241B5xD +Amplifier_Current:INA241B5xDDF +Amplifier_Current:INA241B5xDGK Amplifier_Current:INA253 +Amplifier_Current:INA281A1 +Amplifier_Current:INA281A2 +Amplifier_Current:INA281A3 +Amplifier_Current:INA281A4 +Amplifier_Current:INA281A5 Amplifier_Current:INA282 Amplifier_Current:INA283 Amplifier_Current:INA284 Amplifier_Current:INA285 Amplifier_Current:INA286 +Amplifier_Current:INA293A1 +Amplifier_Current:INA293A2 +Amplifier_Current:INA293A3 +Amplifier_Current:INA293A4 +Amplifier_Current:INA293A5 +Amplifier_Current:INA293B1 +Amplifier_Current:INA293B2 +Amplifier_Current:INA293B3 +Amplifier_Current:INA293B4 +Amplifier_Current:INA293B5 +Amplifier_Current:INA4180A1 +Amplifier_Current:INA4180A2 +Amplifier_Current:INA4180A3 +Amplifier_Current:INA4180A4 Amplifier_Current:LMP8640 Amplifier_Current:LT6106 Amplifier_Current:LTC6102HVxDD @@ -931,15 +1044,21 @@ Amplifier_Difference:AD8207 Amplifier_Difference:AD8276 Amplifier_Difference:AD8475ACPZ Amplifier_Difference:AD8475xRMZ +Amplifier_Difference:ADA4938-1 Amplifier_Difference:ADA4940-1xCP Amplifier_Difference:ADA4940-2 Amplifier_Difference:AMC1100DWV +Amplifier_Difference:AMC1200BDWV +Amplifier_Difference:AMC1300BDWV Amplifier_Difference:AMC1300DWV Amplifier_Difference:INA105KP Amplifier_Difference:INA105KU Amplifier_Difference:LM733CH Amplifier_Difference:LM733CN Amplifier_Difference:LM733H +Amplifier_Difference:LTC1992-x-xMS8 +Amplifier_Difference:THS4521ID +Amplifier_Difference:THS4521IDGK Amplifier_Difference:THS4551xRGT Amplifier_Instrumentation:AD620 Amplifier_Instrumentation:AD623 @@ -954,6 +1073,7 @@ Amplifier_Instrumentation:AD623BNZ Amplifier_Instrumentation:AD623BR Amplifier_Instrumentation:AD623BRZ Amplifier_Instrumentation:AD8230 +Amplifier_Instrumentation:AD8231 Amplifier_Instrumentation:AD8236 Amplifier_Instrumentation:AD8236ARMZ Amplifier_Instrumentation:AD8421 @@ -975,14 +1095,20 @@ Amplifier_Instrumentation:INA326 Amplifier_Instrumentation:INA327 Amplifier_Instrumentation:INA333xxDGK Amplifier_Instrumentation:INA333xxDRG +Amplifier_Instrumentation:INA849D +Amplifier_Instrumentation:INA849DGK Amplifier_Instrumentation:LTC1100xN8 Amplifier_Instrumentation:LTC1100xSW Amplifier_Operational:AD797 Amplifier_Operational:AD8001AN Amplifier_Operational:AD8001AR Amplifier_Operational:AD8015 +Amplifier_Operational:AD8021AR +Amplifier_Operational:AD8021ARM Amplifier_Operational:AD817 Amplifier_Operational:AD8603 +Amplifier_Operational:AD8606ARM +Amplifier_Operational:AD8606ARZ Amplifier_Operational:AD8610xR Amplifier_Operational:AD8610xRM Amplifier_Operational:AD8620 @@ -994,6 +1120,13 @@ Amplifier_Operational:ADA4075-2 Amplifier_Operational:ADA4077-1xR Amplifier_Operational:ADA4077-1xRM Amplifier_Operational:ADA4084-4xCP +Amplifier_Operational:ADA4099-1xUJ +Amplifier_Operational:ADA4099-2xCP +Amplifier_Operational:ADA4099-2xR +Amplifier_Operational:ADA4099-2xRM +Amplifier_Operational:ADA4522-1 +Amplifier_Operational:ADA4522-2 +Amplifier_Operational:ADA4522-4 Amplifier_Operational:ADA4530-1 Amplifier_Operational:ADA4610-1xR Amplifier_Operational:ADA4610-1xRJ @@ -1004,10 +1137,15 @@ Amplifier_Operational:ADA4610-4xCP Amplifier_Operational:ADA4610-4xR Amplifier_Operational:ADA4622-2xCP Amplifier_Operational:ADA4622-4xCP +Amplifier_Operational:ADA4625-1ARDZ +Amplifier_Operational:ADA4625-2ARDZ Amplifier_Operational:ADA4807-1 Amplifier_Operational:ADA4807-2ACP Amplifier_Operational:ADA4807-2ARM -Amplifier_Operational:ADA4807-4 +Amplifier_Operational:ADA4807-4ARUZ +Amplifier_Operational:ADA4817-1ACP +Amplifier_Operational:ADA4817-1ARD +Amplifier_Operational:ADA4817-2ACP Amplifier_Operational:ADA4841-1YRJ Amplifier_Operational:ADA4870ARRZ Amplifier_Operational:ADA4898-1YRDZ @@ -1059,6 +1197,7 @@ Amplifier_Operational:LM7171xIM Amplifier_Operational:LM7171xIN Amplifier_Operational:LM7332 Amplifier_Operational:LM741 +Amplifier_Operational:LM8261 Amplifier_Operational:LMC6062 Amplifier_Operational:LMC6082 Amplifier_Operational:LMC6482 @@ -1070,10 +1209,12 @@ Amplifier_Operational:LMH6609MF Amplifier_Operational:LMH6611 Amplifier_Operational:LMH6702MA Amplifier_Operational:LMH6702MF +Amplifier_Operational:LMH6733 Amplifier_Operational:LMV321 Amplifier_Operational:LMV324 Amplifier_Operational:LMV358 Amplifier_Operational:LMV601 +Amplifier_Operational:LOG114AxRGV Amplifier_Operational:LPV811DBV Amplifier_Operational:LPV812DGK Amplifier_Operational:LT1012 @@ -1148,9 +1289,7 @@ Amplifier_Operational:MCP6L02x-xMS Amplifier_Operational:MCP6L02x-xSN Amplifier_Operational:MCP6L04-xST Amplifier_Operational:MCP6L04x-xSL -Amplifier_Operational:MCP6L91RT-EMS Amplifier_Operational:MCP6L91RT-EOT -Amplifier_Operational:MCP6L91RT-ESN Amplifier_Operational:MCP6L91T-EOT Amplifier_Operational:MCP6L92 Amplifier_Operational:MCP6L94 @@ -1187,11 +1326,17 @@ Amplifier_Operational:OP249GS Amplifier_Operational:OP275 Amplifier_Operational:OP279 Amplifier_Operational:OP77 +Amplifier_Operational:OPA121KM +Amplifier_Operational:OPA121KP +Amplifier_Operational:OPA121KU Amplifier_Operational:OPA134 Amplifier_Operational:OPA1602 Amplifier_Operational:OPA1604 Amplifier_Operational:OPA1612AxD Amplifier_Operational:OPA1641 +Amplifier_Operational:OPA1655D +Amplifier_Operational:OPA1655DBV +Amplifier_Operational:OPA1656ID Amplifier_Operational:OPA1678 Amplifier_Operational:OPA1679 Amplifier_Operational:OPA1692xD @@ -1222,10 +1367,18 @@ Amplifier_Operational:OPA2356xxDGK Amplifier_Operational:OPA2376xxD Amplifier_Operational:OPA2376xxDGK Amplifier_Operational:OPA2376xxYZD +Amplifier_Operational:OPA2604AP Amplifier_Operational:OPA2691 Amplifier_Operational:OPA2691-14 Amplifier_Operational:OPA2695xD Amplifier_Operational:OPA2695xRGT +Amplifier_Operational:OPA2890ID +Amplifier_Operational:OPA2890IDGS +Amplifier_Operational:OPA2994xD +Amplifier_Operational:OPA310SxDBV +Amplifier_Operational:OPA310SxDCK +Amplifier_Operational:OPA310xDBV +Amplifier_Operational:OPA310xDCK Amplifier_Operational:OPA330xxD Amplifier_Operational:OPA330xxDBV Amplifier_Operational:OPA330xxDCK @@ -1259,6 +1412,11 @@ Amplifier_Operational:OPA551U Amplifier_Operational:OPA552P Amplifier_Operational:OPA552U Amplifier_Operational:OPA569DWP +Amplifier_Operational:OPA690xD +Amplifier_Operational:OPA690xDBV +Amplifier_Operational:OPA810xD +Amplifier_Operational:OPA810xDBV +Amplifier_Operational:OPA810xDCK Amplifier_Operational:OPA818xDRG Amplifier_Operational:OPA842xD Amplifier_Operational:OPA842xDBV @@ -1295,6 +1453,20 @@ Amplifier_Operational:TLC272 Amplifier_Operational:TLC274 Amplifier_Operational:TLC277 Amplifier_Operational:TLC279 +Amplifier_Operational:TLC27M2xD +Amplifier_Operational:TLC27M2xPS +Amplifier_Operational:TLC27M2xPW +Amplifier_Operational:TLC27M7xD +Amplifier_Operational:TLC27M7xPS +Amplifier_Operational:TLE2141ACD +Amplifier_Operational:TLE2141ACP +Amplifier_Operational:TLE2141AID +Amplifier_Operational:TLE2141AIP +Amplifier_Operational:TLE2141CD +Amplifier_Operational:TLE2141CP +Amplifier_Operational:TLE2141ID +Amplifier_Operational:TLE2141IP +Amplifier_Operational:TLE2141MD Amplifier_Operational:TLV172IDCK Amplifier_Operational:TLV2371D Amplifier_Operational:TLV2371DBV @@ -1305,10 +1477,12 @@ Amplifier_Operational:TLV9001IDCK Amplifier_Operational:TLV9004xRUCR Amplifier_Operational:TLV9061xDBV Amplifier_Operational:TLV9061xDCK +Amplifier_Operational:TLV9061xDPW Amplifier_Operational:TLV9062 Amplifier_Operational:TLV9062xD Amplifier_Operational:TLV9062xDSG Amplifier_Operational:TLV9064 +Amplifier_Operational:TLV9064xRTE Amplifier_Operational:TLV9301xDBV Amplifier_Operational:TLV9301xDCK Amplifier_Operational:TLV9302xD @@ -1328,6 +1502,8 @@ Amplifier_Operational:TSV912IDT Amplifier_Operational:TSV912IQ2T Amplifier_Operational:TSV912IST Amplifier_Operational:TSV914 +Amplifier_Operational:TSV991AILT +Amplifier_Operational:TSV991AIQ1T Amplifier_Operational:TSV994 Amplifier_Video:AD813 Amplifier_Video:MAX453 @@ -1342,16 +1518,31 @@ Analog:LF398H Analog:LF398_DIP8 Analog:LF398_SOIC14 Analog:LF398_SOIC8 +Analog:LM231N +Analog:LM331N +Analog:LTC1966 +Analog:LTC1967 +Analog:LTC1968 Analog:MLX90314xDF Analog:MLX90320xFR Analog:MPY634KP Analog:MPY634KU Analog:PGA112 Analog:PGA113 +Analog_ADC:AD40xxBCPZ +Analog_ADC:AD40xxBRMZ +Analog_ADC:AD574A Analog_ADC:AD6644 Analog_ADC:AD6645 Analog_ADC:AD7171 Analog_ADC:AD7298 +Analog_ADC:AD7321 +Analog_ADC:AD7322 +Analog_ADC:AD7323 +Analog_ADC:AD7324 +Analog_ADC:AD7327 +Analog_ADC:AD7328 +Analog_ADC:AD7329 Analog_ADC:AD7606 Analog_ADC:AD7606-4 Analog_ADC:AD7606-6 @@ -1360,6 +1551,7 @@ Analog_ADC:AD7682BCP Analog_ADC:AD7689xCP Analog_ADC:AD7699BCP Analog_ADC:AD7722 +Analog_ADC:AD7745 Analog_ADC:AD7746 Analog_ADC:AD7794 Analog_ADC:AD7795 @@ -1375,6 +1567,7 @@ Analog_ADC:ADC101C021CIMK Analog_ADC:ADC101C021CIMM Analog_ADC:ADC1173 Analog_ADC:ADC121C021CIMM +Analog_ADC:ADC1283 Analog_ADC:ADC128D818 Analog_ADC:ADS1013IDGS Analog_ADC:ADS1014IDGS @@ -1392,6 +1585,7 @@ Analog_ADC:ADS1232IPW Analog_ADC:ADS1234IPW Analog_ADC:ADS1243 Analog_ADC:ADS1251 +Analog_ADC:ADS127L01IPBS Analog_ADC:ADS1298xPAG Analog_ADC:ADS7029 Analog_ADC:ADS7039 @@ -1482,21 +1676,22 @@ Analog_ADC:MCP3201 Analog_ADC:MCP3202 Analog_ADC:MCP3204 Analog_ADC:MCP3208 +Analog_ADC:MCP3221 Analog_ADC:MCP3301 Analog_ADC:MCP3421A0T-ECH -Analog_ADC:MCP3422 -Analog_ADC:MCP3423 -Analog_ADC:MCP3424 -Analog_ADC:MCP3425A0T-ECH -Analog_ADC:MCP3425A1T-ECH -Analog_ADC:MCP3425A2T-ECH -Analog_ADC:MCP3425A3T-ECH -Analog_ADC:MCP3426-xMC -Analog_ADC:MCP3426-xMS -Analog_ADC:MCP3426-xSN -Analog_ADC:MCP3427-xMF -Analog_ADC:MCP3427-xUN -Analog_ADC:MCP3428 +Analog_ADC:MCP3422Axx-xMS +Analog_ADC:MCP3422Axx-xSN +Analog_ADC:MCP3423x-xUN +Analog_ADC:MCP3424x-xSL +Analog_ADC:MCP3424x-xST +Analog_ADC:MCP3425Axx-xCH +Analog_ADC:MCP3426Axx-xMC +Analog_ADC:MCP3426Axx-xMS +Analog_ADC:MCP3426Axx-xSN +Analog_ADC:MCP3427x-xMF +Analog_ADC:MCP3427x-xUN +Analog_ADC:MCP3428x-xSL +Analog_ADC:MCP3428x-xST Analog_ADC:MCP3550-50-EMS Analog_ADC:MCP3550-60-ESN Analog_ADC:MCP3551-EMS @@ -1516,6 +1711,7 @@ Analog_DAC:AD5689BRUZ Analog_DAC:AD5689RxCPZ Analog_DAC:AD5689RxRUZ Analog_DAC:AD5691RxRM +Analog_DAC:AD5692RxRM Analog_DAC:AD5693RxRM Analog_DAC:AD5697RBCPZ Analog_DAC:AD5697RBRUZ @@ -1562,27 +1758,46 @@ Analog_DAC:AD9106BCP Analog_DAC:AD9142 Analog_DAC:AD9744 Analog_DAC:ADS7830 +Analog_DAC:CS434x-xZZ Analog_DAC:DAC08 Analog_DAC:DAC0808_DIP Analog_DAC:DAC0808_SOIC Analog_DAC:DAC081C081CIMK +Analog_DAC:DAC1006LCN +Analog_DAC:DAC1006LCWM +Analog_DAC:DAC1007LCN +Analog_DAC:DAC1008LCN Analog_DAC:DAC101C081CIMK Analog_DAC:DAC121C081CIMK Analog_DAC:DAC1220E Analog_DAC:DAC5311xDCK Analog_DAC:DAC5578xPW Analog_DAC:DAC5578xRGE +Analog_DAC:DAC60502 +Analog_DAC:DAC60504 Analog_DAC:DAC6311xDCK Analog_DAC:DAC6578xPW Analog_DAC:DAC6578xRGE +Analog_DAC:DAC70502 +Analog_DAC:DAC70504 Analog_DAC:DAC7311xDCK Analog_DAC:DAC7513_DCN Analog_DAC:DAC7565 Analog_DAC:DAC7578xPW Analog_DAC:DAC7578xRGE Analog_DAC:DAC7750xRHA +Analog_DAC:DAC80502 +Analog_DAC:DAC80504 Analog_DAC:DAC8165 +Analog_DAC:DAC8501E +Analog_DAC:DAC8531E +Analog_DAC:DAC8531IDRB +Analog_DAC:DAC8550IxDGK +Analog_DAC:DAC8551IxDGK +Analog_DAC:DAC8552 +Analog_DAC:DAC8560IxDGK Analog_DAC:DAC8565 +Analog_DAC:DAC8571IDGK Analog_DAC:DAC8750xRHA Analog_DAC:LTC1257 Analog_DAC:LTC1446 @@ -1655,6 +1870,7 @@ Analog_Switch:ADG729 Analog_Switch:ADG733BRQ Analog_Switch:ADG733BRU Analog_Switch:ADG734 +Analog_Switch:ADG758CPZ Analog_Switch:ADG824BCP Analog_Switch:ADG884xCP Analog_Switch:ADG884xRM @@ -1714,7 +1930,9 @@ Analog_Switch:DG9421DV Analog_Switch:DG9422DV Analog_Switch:FSA3157L6X Analog_Switch:FSA3157P6X +Analog_Switch:HEF4066BT Analog_Switch:HI524 +Analog_Switch:MAX14662 Analog_Switch:MAX14759 Analog_Switch:MAX14761 Analog_Switch:MAX14778 @@ -1749,20 +1967,30 @@ Analog_Switch:MAX40200ANS Analog_Switch:MAX40200AUK Analog_Switch:NC7SB3157L6X Analog_Switch:NC7SB3157P6X +Analog_Switch:NX3L4051HR +Analog_Switch:NX3L4051PW Analog_Switch:SN74CBT3253 +Analog_Switch:TMUX1101DBV +Analog_Switch:TMUX1101DCK +Analog_Switch:TMUX1102DBV +Analog_Switch:TMUX1102DCK Analog_Switch:TMUX1108PW -Analog_Switch:TS3A24159DGSR -Analog_Switch:TS3A24159DRCR -Analog_Switch:TS3A24159YZPR +Analog_Switch:TMUX154EDGS +Analog_Switch:TMUX154ERSW +Analog_Switch:TS3A24159DGS +Analog_Switch:TS3A24159DRC +Analog_Switch:TS3A24159YZP Analog_Switch:TS3A27518EPW Analog_Switch:TS3A27518ERTW Analog_Switch:TS3A5017RGY +Analog_Switch:TS3A5017RSV +Analog_Switch:TS3A5223RSW Analog_Switch:TS3DS10224RUK Analog_Switch:TS3L501ERUA Analog_Switch:TS5A23159DGS Analog_Switch:TS5A23159RSE Analog_Switch:TS5A3159ADBVR -Analog_Switch:TS5A3159ADCKR +Analog_Switch:TS5A3159ADCK Analog_Switch:TS5A3159AYZPR Analog_Switch:TS5A3159DBV Analog_Switch:TS5A3159DCK @@ -1820,13 +2048,18 @@ Audio:ISD2575S Audio:ISD2590E Audio:ISD2590P Audio:ISD2590S +Audio:MAX98357A +Audio:MAX98357B Audio:MN3005 Audio:MN3007 Audio:MN3207 Audio:MSGEQ7 +Audio:PCM1754DBQ +Audio:PCM1780 Audio:PCM1792A Audio:PCM1794A Audio:PCM2902 +Audio:PCM3060 Audio:PCM5100 Audio:PCM5100A Audio:PCM5101 @@ -1835,16 +2068,22 @@ Audio:PCM5102 Audio:PCM5102A Audio:PCM5121PW Audio:PCM5122PW +Audio:PGA2310PA +Audio:PGA2310UA +Audio:PGA2500 Audio:PGA4311 Audio:PT2258 Audio:PT2258-S Audio:PT2399 Audio:RD5106A Audio:RD5107A +Audio:RE46C317 +Audio:RE46C318 Audio:SAD1024 Audio:SAD512 Audio:SGTL5000XNAA3 Audio:SGTL5000XNLA3 +Audio:SPN1001 Audio:SRC4392xPFB Audio:SSI2144 Audio:SSI2164 @@ -1873,8 +2112,10 @@ Battery_Management:ADP5091 Battery_Management:ADP5092 Battery_Management:AP9101CK Battery_Management:AP9101CK6 +Battery_Management:APW7261 Battery_Management:AS8506C Battery_Management:BQ2003 +Battery_Management:BQ21040DBV Battery_Management:BQ24004 Battery_Management:BQ24005 Battery_Management:BQ24006 @@ -1889,10 +2130,16 @@ Battery_Management:BQ24090DGQ Battery_Management:BQ24133RGY Battery_Management:BQ24166RGE Battery_Management:BQ24167RGE +Battery_Management:BQ24610 +Battery_Management:BQ24617 +Battery_Management:BQ24650 Battery_Management:BQ2501x +Battery_Management:BQ25040 +Battery_Management:BQ25173DSG Battery_Management:BQ25504 Battery_Management:BQ25570 Battery_Management:BQ25601 +Battery_Management:BQ25886RGE Battery_Management:BQ25887 Battery_Management:BQ25895RTW Battery_Management:BQ27441-G1 @@ -1914,10 +2161,12 @@ Battery_Management:BQ76940DBT Battery_Management:BQ78350DBT Battery_Management:BQ78350DBT-R1 Battery_Management:DS2745U +Battery_Management:DW01A Battery_Management:LC709203FQH-01TWG Battery_Management:LC709203FQH-02TWG Battery_Management:LC709203FQH-03TWG Battery_Management:LC709203FQH-04TWG +Battery_Management:LGS5500EP Battery_Management:LP3947 Battery_Management:LT3652EDD Battery_Management:LT3652EMSE @@ -1925,6 +2174,7 @@ Battery_Management:LT3652IDD Battery_Management:LT3652IMSE Battery_Management:LTC2942 Battery_Management:LTC2942-1 +Battery_Management:LTC2959 Battery_Management:LTC3553 Battery_Management:LTC3555 Battery_Management:LTC3555-1 @@ -1943,6 +2193,7 @@ Battery_Management:LTC4055 Battery_Management:LTC4055-1 Battery_Management:LTC4060EDHC Battery_Management:LTC4060EFE +Battery_Management:LTC4067EDE Battery_Management:LTC4156 Battery_Management:LTC6803-2 Battery_Management:LTC6803-4 @@ -1971,13 +2222,21 @@ Battery_Management:MCP73811T-420I-OT Battery_Management:MCP73811T-435I-OT Battery_Management:MCP73812T-420I-OT Battery_Management:MCP73812T-435I-OT +Battery_Management:MCP73831-2-MC Battery_Management:MCP73831-2-OT +Battery_Management:MCP73831-3-MC Battery_Management:MCP73831-3-OT +Battery_Management:MCP73831-4-MC Battery_Management:MCP73831-4-OT +Battery_Management:MCP73831-5-MC Battery_Management:MCP73831-5-OT +Battery_Management:MCP73832-2-MC Battery_Management:MCP73832-2-OT +Battery_Management:MCP73832-3-MC Battery_Management:MCP73832-3-OT +Battery_Management:MCP73832-4-MC Battery_Management:MCP73832-4-OT +Battery_Management:MCP73832-5-MC Battery_Management:MCP73832-5-OT Battery_Management:MCP73833-xxx-MF Battery_Management:MCP73833-xxx-UN @@ -1992,6 +2251,10 @@ Battery_Management:MCP73871-3CA Battery_Management:MCP73871-3CC Battery_Management:MCP73871-4CA Battery_Management:MCP73871-4CC +Battery_Management:SLM6800 +Battery_Management:TP4056-42-ESOP8 +Battery_Management:TP4057 +Buffer:CDCV304 Buffer:PI6C5946002ZH Comparator:AD8561 Comparator:ADCMP350 @@ -2048,8 +2311,12 @@ Comparator:MIC845L Comparator:MIC845N Comparator:TL3116 Comparator:TL331 +Comparator:TLV3501AID +Comparator:TLV3501AIDBV Comparator:TLV7031DBV Comparator:TLV7041DBV +Comparator:TLV7041DCK +Comparator:TLV7041SDCK Connector:4P2C Connector:4P2C_Shielded Connector:4P4C @@ -2176,42 +2443,86 @@ Connector:Conn_01x39_Pin Connector:Conn_01x39_Socket Connector:Conn_01x40_Pin Connector:Conn_01x40_Socket +Connector:Conn_01x41_Pin +Connector:Conn_01x41_Socket +Connector:Conn_01x42_Pin +Connector:Conn_01x42_Socket +Connector:Conn_01x43_Pin +Connector:Conn_01x43_Socket +Connector:Conn_01x44_Pin +Connector:Conn_01x44_Socket +Connector:Conn_01x45_Pin +Connector:Conn_01x45_Socket +Connector:Conn_01x46_Pin +Connector:Conn_01x46_Socket +Connector:Conn_01x47_Pin +Connector:Conn_01x47_Socket +Connector:Conn_01x48_Pin +Connector:Conn_01x48_Socket +Connector:Conn_01x49_Pin +Connector:Conn_01x49_Socket +Connector:Conn_01x50_Pin +Connector:Conn_01x50_Socket +Connector:Conn_01x51_Pin +Connector:Conn_01x51_Socket +Connector:Conn_01x52_Pin +Connector:Conn_01x52_Socket +Connector:Conn_01x53_Pin +Connector:Conn_01x53_Socket +Connector:Conn_01x54_Pin +Connector:Conn_01x54_Socket +Connector:Conn_01x55_Pin +Connector:Conn_01x55_Socket +Connector:Conn_01x56_Pin +Connector:Conn_01x56_Socket +Connector:Conn_01x57_Pin +Connector:Conn_01x57_Socket +Connector:Conn_01x58_Pin +Connector:Conn_01x58_Socket +Connector:Conn_01x59_Pin +Connector:Conn_01x59_Socket +Connector:Conn_01x60_Pin +Connector:Conn_01x60_Socket Connector:Conn_15X4 +Connector:Conn_ARM_Cortex_Debug_ETM_20 Connector:Conn_ARM_JTAG_SWD_10 Connector:Conn_ARM_JTAG_SWD_20 Connector:Conn_ARM_SWD_TagConnect_TC2030 Connector:Conn_ARM_SWD_TagConnect_TC2030-NL Connector:Conn_Coaxial Connector:Conn_Coaxial_Power +Connector:Conn_Coaxial_Small Connector:Conn_Coaxial_x2 Connector:Conn_Coaxial_x2_Isolated Connector:Conn_PIC_ICSP_ICD +Connector:Conn_Plug_2P +Connector:Conn_Plug_3P_Protected +Connector:Conn_Receptacle_2P +Connector:Conn_Receptacle_3P_Protected Connector:Conn_ST_STDC14 +Connector:Conn_Shielded_Pair Connector:Conn_Triaxial -Connector:Conn_WallPlug -Connector:Conn_WallPlug_Earth -Connector:Conn_WallSocket -Connector:Conn_WallSocket_Earth -Connector:DA15_Plug -Connector:DA15_Plug_MountingHoles -Connector:DA15_Receptacle -Connector:DA15_Receptacle_MountingHoles -Connector:DB25_Plug -Connector:DB25_Plug_MountingHoles -Connector:DB25_Receptacle -Connector:DB25_Receptacle_MountingHoles -Connector:DC37_Plug -Connector:DC37_Plug_MountingHoles -Connector:DC37_Receptacle -Connector:DC37_Receptacle_MountingHoles -Connector:DE15_Plug_HighDensity -Connector:DE15_Plug_HighDensity_MountingHoles -Connector:DE15_Receptacle_HighDensity -Connector:DE15_Receptacle_HighDensity_MountingHoles -Connector:DE9_Plug -Connector:DE9_Plug_MountingHoles -Connector:DE9_Receptacle -Connector:DE9_Receptacle_MountingHoles +Connector:Conn_Triaxial_Same_Side +Connector:DA15_Pins +Connector:DA15_Pins_MountingHoles +Connector:DA15_Socket +Connector:DA15_Socket_MountingHoles +Connector:DB25_Pins +Connector:DB25_Pins_MountingHoles +Connector:DB25_Socket +Connector:DB25_Socket_MountingHoles +Connector:DC37_Pins +Connector:DC37_Pins_MountingHoles +Connector:DC37_Socket +Connector:DC37_Socket_MountingHoles +Connector:DE15_Pins_HighDensity +Connector:DE15_Pins_HighDensity_MountingHoles +Connector:DE15_Socket_HighDensity +Connector:DE15_Socket_HighDensity_MountingHoles +Connector:DE9_Pins +Connector:DE9_Pins_MountingHoles +Connector:DE9_Socket +Connector:DE9_Socket_MountingHoles Connector:DIN-3 Connector:DIN-4 Connector:DIN-5 @@ -2245,6 +2556,8 @@ Connector:DIN41612_02x32_AB Connector:DIN41612_02x32_AC Connector:DIN41612_02x32_AE Connector:DIN41612_02x32_ZB +Connector:DVI-D_Dual_Link +Connector:DVI-I_Dual_Link Connector:ExpressCard Connector:HDMI_A Connector:HDMI_A_1.4 @@ -2253,6 +2566,16 @@ Connector:HDMI_C_1.3 Connector:HDMI_C_1.4 Connector:HDMI_D_1.4 Connector:HDMI_E +Connector:IEC61076-2_M8_A-coding_01x03_Plug_Shielded +Connector:IEC61076-2_M8_A-coding_01x03_Receptacle_Shielded +Connector:IEC61076-2_M8_A-coding_01x04_Plug_Shielded +Connector:IEC61076-2_M8_A-coding_01x04_Receptacle_Shielded +Connector:IEC_60320_C13_Plug +Connector:IEC_60320_C14_Receptacle +Connector:IEC_60320_C5_Plug +Connector:IEC_60320_C6_Receptacle +Connector:IEC_60320_C7_Plug +Connector:IEC_60320_C8_Receptacle Connector:IEEE1394a Connector:JAE_SIM_Card_SF72S006 Connector:Jack-DC @@ -2310,8 +2633,10 @@ Connector:RJ45_Bel_V895-1001-AW Connector:RJ45_Halo_HFJ11-x2450E-LxxRL Connector:RJ45_Halo_HFJ11-x2450ERL Connector:RJ45_Halo_HFJ11-x2450HRL +Connector:RJ45_Hanrun_HR911105A_Horizontal Connector:RJ45_JK00177 Connector:RJ45_JK0654219 +Connector:RJ45_Kycon_G7LX-A88S7-BP-GY Connector:RJ45_LED Connector:RJ45_LED_Shielded Connector:RJ45_LED_Shielded_x2 @@ -2320,6 +2645,7 @@ Connector:RJ45_RB1-125B8G1A Connector:RJ45_Shielded Connector:RJ45_Wuerth_74980111211 Connector:RJ45_Wuerth_7499010121A +Connector:RJ45_Wuerth_7499010211A Connector:RJ45_Wuerth_7499151120 Connector:RJ48 Connector:RJ48_Shielded @@ -2329,9 +2655,10 @@ Connector:RJ61 Connector:RJ61_Shielded Connector:RJ9 Connector:RJ9_Shielded -Connector:Raspberry_Pi_2_3 +Connector:Raspberry_Pi_4 Connector:SCART-F -Connector:SD_Card +Connector:SD_Card_Device +Connector:SD_Card_Receptacle Connector:SIM_Card Connector:SIM_Card_Shielded Connector:SODIMM-200 @@ -2358,6 +2685,9 @@ Connector:Screw_Terminal_01x17 Connector:Screw_Terminal_01x18 Connector:Screw_Terminal_01x19 Connector:Screw_Terminal_01x20 +Connector:TC2030 +Connector:TC2050 +Connector:TC2070 Connector:TestPoint Connector:TestPoint_2Pole Connector:TestPoint_Alt @@ -2378,8 +2708,10 @@ Connector:USB_B_Mini Connector:USB_C_Plug Connector:USB_C_Plug_USB2.0 Connector:USB_C_Receptacle +Connector:USB_C_Receptacle_PowerOnly_24P Connector:USB_C_Receptacle_PowerOnly_6P -Connector:USB_C_Receptacle_USB2.0 +Connector:USB_C_Receptacle_USB2.0_14P +Connector:USB_C_Receptacle_USB2.0_16P Connector:USB_OTG Connector_Audio:AudioJack2 Connector_Audio:AudioJack2_Dual_Ground_Switch @@ -2654,6 +2986,26 @@ Connector_Generic:Conn_01x37 Connector_Generic:Conn_01x38 Connector_Generic:Conn_01x39 Connector_Generic:Conn_01x40 +Connector_Generic:Conn_01x41 +Connector_Generic:Conn_01x42 +Connector_Generic:Conn_01x43 +Connector_Generic:Conn_01x44 +Connector_Generic:Conn_01x45 +Connector_Generic:Conn_01x46 +Connector_Generic:Conn_01x47 +Connector_Generic:Conn_01x48 +Connector_Generic:Conn_01x49 +Connector_Generic:Conn_01x50 +Connector_Generic:Conn_01x51 +Connector_Generic:Conn_01x52 +Connector_Generic:Conn_01x53 +Connector_Generic:Conn_01x54 +Connector_Generic:Conn_01x55 +Connector_Generic:Conn_01x56 +Connector_Generic:Conn_01x57 +Connector_Generic:Conn_01x58 +Connector_Generic:Conn_01x59 +Connector_Generic:Conn_01x60 Connector_Generic:Conn_02x01 Connector_Generic:Conn_02x01_Row_Letter_First Connector_Generic:Conn_02x01_Row_Letter_Last @@ -2852,6 +3204,46 @@ Connector_Generic:Conn_02x40_Odd_Even Connector_Generic:Conn_02x40_Row_Letter_First Connector_Generic:Conn_02x40_Row_Letter_Last Connector_Generic:Conn_02x40_Top_Bottom +Connector_Generic:Conn_02x41_Row_Letter_First +Connector_Generic:Conn_02x41_Row_Letter_Last +Connector_Generic:Conn_02x42_Row_Letter_First +Connector_Generic:Conn_02x42_Row_Letter_Last +Connector_Generic:Conn_02x43_Row_Letter_First +Connector_Generic:Conn_02x43_Row_Letter_Last +Connector_Generic:Conn_02x44_Row_Letter_First +Connector_Generic:Conn_02x44_Row_Letter_Last +Connector_Generic:Conn_02x45_Row_Letter_First +Connector_Generic:Conn_02x45_Row_Letter_Last +Connector_Generic:Conn_02x46_Row_Letter_First +Connector_Generic:Conn_02x46_Row_Letter_Last +Connector_Generic:Conn_02x47_Row_Letter_First +Connector_Generic:Conn_02x47_Row_Letter_Last +Connector_Generic:Conn_02x48_Row_Letter_First +Connector_Generic:Conn_02x48_Row_Letter_Last +Connector_Generic:Conn_02x49_Row_Letter_First +Connector_Generic:Conn_02x49_Row_Letter_Last +Connector_Generic:Conn_02x50_Row_Letter_First +Connector_Generic:Conn_02x50_Row_Letter_Last +Connector_Generic:Conn_02x51_Row_Letter_First +Connector_Generic:Conn_02x51_Row_Letter_Last +Connector_Generic:Conn_02x52_Row_Letter_First +Connector_Generic:Conn_02x52_Row_Letter_Last +Connector_Generic:Conn_02x53_Row_Letter_First +Connector_Generic:Conn_02x53_Row_Letter_Last +Connector_Generic:Conn_02x54_Row_Letter_First +Connector_Generic:Conn_02x54_Row_Letter_Last +Connector_Generic:Conn_02x55_Row_Letter_First +Connector_Generic:Conn_02x55_Row_Letter_Last +Connector_Generic:Conn_02x56_Row_Letter_First +Connector_Generic:Conn_02x56_Row_Letter_Last +Connector_Generic:Conn_02x57_Row_Letter_First +Connector_Generic:Conn_02x57_Row_Letter_Last +Connector_Generic:Conn_02x58_Row_Letter_First +Connector_Generic:Conn_02x58_Row_Letter_Last +Connector_Generic:Conn_02x59_Row_Letter_First +Connector_Generic:Conn_02x59_Row_Letter_Last +Connector_Generic:Conn_02x60_Row_Letter_First +Connector_Generic:Conn_02x60_Row_Letter_Last Connector_Generic:Conn_2Rows-05Pins Connector_Generic:Conn_2Rows-07Pins Connector_Generic:Conn_2Rows-09Pins @@ -2928,6 +3320,26 @@ Connector_Generic_MountingPin:Conn_01x37_MountingPin Connector_Generic_MountingPin:Conn_01x38_MountingPin Connector_Generic_MountingPin:Conn_01x39_MountingPin Connector_Generic_MountingPin:Conn_01x40_MountingPin +Connector_Generic_MountingPin:Conn_01x41_MountingPin +Connector_Generic_MountingPin:Conn_01x42_MountingPin +Connector_Generic_MountingPin:Conn_01x43_MountingPin +Connector_Generic_MountingPin:Conn_01x44_MountingPin +Connector_Generic_MountingPin:Conn_01x45_MountingPin +Connector_Generic_MountingPin:Conn_01x46_MountingPin +Connector_Generic_MountingPin:Conn_01x47_MountingPin +Connector_Generic_MountingPin:Conn_01x48_MountingPin +Connector_Generic_MountingPin:Conn_01x49_MountingPin +Connector_Generic_MountingPin:Conn_01x50_MountingPin +Connector_Generic_MountingPin:Conn_01x51_MountingPin +Connector_Generic_MountingPin:Conn_01x52_MountingPin +Connector_Generic_MountingPin:Conn_01x53_MountingPin +Connector_Generic_MountingPin:Conn_01x54_MountingPin +Connector_Generic_MountingPin:Conn_01x55_MountingPin +Connector_Generic_MountingPin:Conn_01x56_MountingPin +Connector_Generic_MountingPin:Conn_01x57_MountingPin +Connector_Generic_MountingPin:Conn_01x58_MountingPin +Connector_Generic_MountingPin:Conn_01x59_MountingPin +Connector_Generic_MountingPin:Conn_01x60_MountingPin Connector_Generic_MountingPin:Conn_02x01_MountingPin Connector_Generic_MountingPin:Conn_02x01_Row_Letter_First_MountingPin Connector_Generic_MountingPin:Conn_02x01_Row_Letter_Last_MountingPin @@ -3126,6 +3538,46 @@ Connector_Generic_MountingPin:Conn_02x40_Odd_Even_MountingPin Connector_Generic_MountingPin:Conn_02x40_Row_Letter_First_MountingPin Connector_Generic_MountingPin:Conn_02x40_Row_Letter_Last_MountingPin Connector_Generic_MountingPin:Conn_02x40_Top_Bottom_MountingPin +Connector_Generic_MountingPin:Conn_02x41_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x41_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x42_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x42_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x43_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x43_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x44_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x44_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x45_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x45_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x46_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x46_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x47_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x47_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x48_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x48_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x49_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x49_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x50_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x50_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x51_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x51_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x52_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x52_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x53_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x53_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x54_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x54_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x55_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x55_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x56_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x56_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x57_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x57_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x58_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x58_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x59_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x59_Row_Letter_Last_MountingPin +Connector_Generic_MountingPin:Conn_02x60_Row_Letter_First_MountingPin +Connector_Generic_MountingPin:Conn_02x60_Row_Letter_Last_MountingPin Connector_Generic_MountingPin:Conn_2Rows-05Pins_MountingPin Connector_Generic_MountingPin:Conn_2Rows-07Pins_MountingPin Connector_Generic_MountingPin:Conn_2Rows-09Pins_MountingPin @@ -3202,6 +3654,26 @@ Connector_Generic_Shielded:Conn_01x37_Shielded Connector_Generic_Shielded:Conn_01x38_Shielded Connector_Generic_Shielded:Conn_01x39_Shielded Connector_Generic_Shielded:Conn_01x40_Shielded +Connector_Generic_Shielded:Conn_01x41_Shielded +Connector_Generic_Shielded:Conn_01x42_Shielded +Connector_Generic_Shielded:Conn_01x43_Shielded +Connector_Generic_Shielded:Conn_01x44_Shielded +Connector_Generic_Shielded:Conn_01x45_Shielded +Connector_Generic_Shielded:Conn_01x46_Shielded +Connector_Generic_Shielded:Conn_01x47_Shielded +Connector_Generic_Shielded:Conn_01x48_Shielded +Connector_Generic_Shielded:Conn_01x49_Shielded +Connector_Generic_Shielded:Conn_01x50_Shielded +Connector_Generic_Shielded:Conn_01x51_Shielded +Connector_Generic_Shielded:Conn_01x52_Shielded +Connector_Generic_Shielded:Conn_01x53_Shielded +Connector_Generic_Shielded:Conn_01x54_Shielded +Connector_Generic_Shielded:Conn_01x55_Shielded +Connector_Generic_Shielded:Conn_01x56_Shielded +Connector_Generic_Shielded:Conn_01x57_Shielded +Connector_Generic_Shielded:Conn_01x58_Shielded +Connector_Generic_Shielded:Conn_01x59_Shielded +Connector_Generic_Shielded:Conn_01x60_Shielded Connector_Generic_Shielded:Conn_02x01_Row_Letter_First_Shielded Connector_Generic_Shielded:Conn_02x01_Row_Letter_Last_Shielded Connector_Generic_Shielded:Conn_02x01_Shielded @@ -3400,6 +3872,47 @@ Connector_Generic_Shielded:Conn_02x40_Odd_Even_Shielded Connector_Generic_Shielded:Conn_02x40_Row_Letter_First_Shielded Connector_Generic_Shielded:Conn_02x40_Row_Letter_Last_Shielded Connector_Generic_Shielded:Conn_02x40_Top_Bottom_Shielded +Connector_Generic_Shielded:Conn_02x40_Top_Bottom_Shielded_1 +Connector_Generic_Shielded:Conn_02x41_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x41_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x42_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x42_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x43_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x43_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x44_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x44_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x45_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x45_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x46_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x46_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x47_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x47_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x48_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x48_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x49_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x49_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x50_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x50_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x51_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x51_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x52_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x52_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x53_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x53_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x54_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x54_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x55_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x55_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x56_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x56_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x57_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x57_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x58_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x58_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x59_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x59_Row_Letter_Last_Shielded +Connector_Generic_Shielded:Conn_02x60_Row_Letter_First_Shielded +Connector_Generic_Shielded:Conn_02x60_Row_Letter_Last_Shielded Connector_Generic_Shielded:Conn_2Rows-05Pins_Shielded Connector_Generic_Shielded:Conn_2Rows-07Pins_Shielded Connector_Generic_Shielded:Conn_2Rows-09Pins_Shielded @@ -3443,6 +3956,33 @@ Converter_ACDC:HLK-10M09 Converter_ACDC:HLK-10M12 Converter_ACDC:HLK-10M15 Converter_ACDC:HLK-10M24 +Converter_ACDC:HLK-12M03A +Converter_ACDC:HLK-12M05A +Converter_ACDC:HLK-12M09A +Converter_ACDC:HLK-12M12A +Converter_ACDC:HLK-12M15A +Converter_ACDC:HLK-12M24A +Converter_ACDC:HLK-20M05 +Converter_ACDC:HLK-20M09 +Converter_ACDC:HLK-20M12 +Converter_ACDC:HLK-20M15 +Converter_ACDC:HLK-20M24 +Converter_ACDC:HLK-2M03 +Converter_ACDC:HLK-2M05 +Converter_ACDC:HLK-2M09 +Converter_ACDC:HLK-2M12 +Converter_ACDC:HLK-2M15 +Converter_ACDC:HLK-2M24 +Converter_ACDC:HLK-30M05 +Converter_ACDC:HLK-30M05C +Converter_ACDC:HLK-30M09 +Converter_ACDC:HLK-30M09C +Converter_ACDC:HLK-30M12 +Converter_ACDC:HLK-30M12C +Converter_ACDC:HLK-30M15 +Converter_ACDC:HLK-30M15C +Converter_ACDC:HLK-30M24 +Converter_ACDC:HLK-30M24C Converter_ACDC:HLK-5M03 Converter_ACDC:HLK-5M05 Converter_ACDC:HLK-5M09 @@ -3451,7 +3991,9 @@ Converter_ACDC:HLK-5M15 Converter_ACDC:HLK-5M24 Converter_ACDC:HLK-PM01 Converter_ACDC:HLK-PM03 +Converter_ACDC:HLK-PM09 Converter_ACDC:HLK-PM12 +Converter_ACDC:HLK-PM15 Converter_ACDC:HLK-PM24 Converter_ACDC:HS-40003 Converter_ACDC:HS-40005 @@ -3504,6 +4046,16 @@ Converter_ACDC:IRM-60-15 Converter_ACDC:IRM-60-24 Converter_ACDC:IRM-60-48 Converter_ACDC:IRM-60-5 +Converter_ACDC:MFM-10-12 +Converter_ACDC:MFM-10-15 +Converter_ACDC:MFM-10-24 +Converter_ACDC:MFM-10-3.3 +Converter_ACDC:MFM-10-5 +Converter_ACDC:MFM-15-12 +Converter_ACDC:MFM-15-15 +Converter_ACDC:MFM-15-24 +Converter_ACDC:MFM-15-3.3 +Converter_ACDC:MFM-15-5 Converter_ACDC:PBO-3-S12 Converter_ACDC:PBO-3-S15 Converter_ACDC:PBO-3-S24 @@ -3541,6 +4093,22 @@ Converter_ACDC:RAC20-15DK Converter_ACDC:RAC20-15SK Converter_ACDC:RAC20-24SK Converter_ACDC:RAC20-48SK +Converter_ACDC:TMF05105 +Converter_ACDC:TMF05112 +Converter_ACDC:TMF05115 +Converter_ACDC:TMF05124 +Converter_ACDC:TMF10105 +Converter_ACDC:TMF10112 +Converter_ACDC:TMF10115 +Converter_ACDC:TMF10124 +Converter_ACDC:TMF20105 +Converter_ACDC:TMF20112 +Converter_ACDC:TMF20115 +Converter_ACDC:TMF20124 +Converter_ACDC:TMF30105 +Converter_ACDC:TMF30112 +Converter_ACDC:TMF30115 +Converter_ACDC:TMF30124 Converter_ACDC:TMLM04103 Converter_ACDC:TMLM04105 Converter_ACDC:TMLM04109 @@ -3610,6 +4178,7 @@ Converter_DCDC:ATA00F18S-L Converter_DCDC:ATA00F36S-L Converter_DCDC:ATA00H18S-L Converter_DCDC:ATA00H36S-L +Converter_DCDC:Ag9905LP Converter_DCDC:BD8314NUV Converter_DCDC:IA0305D Converter_DCDC:IA0305S @@ -4045,12 +4614,16 @@ Converter_DCDC:MGJ2D242005SC Converter_DCDC:MGJ3T05150505MC Converter_DCDC:MGJ3T12150505MC Converter_DCDC:MGJ3T24150505MC +Converter_DCDC:MYRGPxx0060x21RC +Converter_DCDC:MYRGPxx0060x21RF Converter_DCDC:NCS1S1203SC Converter_DCDC:NCS1S1205SC Converter_DCDC:NCS1S1212SC Converter_DCDC:NCS1S2403SC Converter_DCDC:NCS1S2405SC Converter_DCDC:NCS1S2412SC +Converter_DCDC:NSD10-xxDyy +Converter_DCDC:NSD10-xxSyy Converter_DCDC:OKI-78SR-12_1.0-W36-C Converter_DCDC:OKI-78SR-12_1.0-W36H-C Converter_DCDC:OKI-78SR-3.3_1.5-W36-C @@ -4119,6 +4692,15 @@ Converter_DCDC:TBA2-2423 Converter_DCDC:TC7662AxPA Converter_DCDC:TC7662Bx0A Converter_DCDC:TC7662BxPA +Converter_DCDC:TDU1-0511 +Converter_DCDC:TDU1-0512 +Converter_DCDC:TDU1-0513 +Converter_DCDC:TDU1-1211 +Converter_DCDC:TDU1-1212 +Converter_DCDC:TDU1-1213 +Converter_DCDC:TDU1-2411 +Converter_DCDC:TDU1-2412 +Converter_DCDC:TDU1-2413 Converter_DCDC:TEA1-0505 Converter_DCDC:TEA1-0505E Converter_DCDC:TEA1-0505HI @@ -4140,6 +4722,58 @@ Converter_DCDC:TEC2-4812WI Converter_DCDC:TEC2-4813WI Converter_DCDC:TEC2-4815WI Converter_DCDC:TEC2-4819WI +Converter_DCDC:TEC3-2410UI +Converter_DCDC:TEC3-2411UI +Converter_DCDC:TEC3-2412UI +Converter_DCDC:TEC3-2413UI +Converter_DCDC:TEC3-2421UI +Converter_DCDC:TEC3-2422UI +Converter_DCDC:TEC3-2423UI +Converter_DCDC:TEL12-1211 +Converter_DCDC:TEL12-1212 +Converter_DCDC:TEL12-1213 +Converter_DCDC:TEL12-1215 +Converter_DCDC:TEL12-1222 +Converter_DCDC:TEL12-1223 +Converter_DCDC:TEL12-2411 +Converter_DCDC:TEL12-2411WI +Converter_DCDC:TEL12-2412 +Converter_DCDC:TEL12-2412WI +Converter_DCDC:TEL12-2413 +Converter_DCDC:TEL12-2413WI +Converter_DCDC:TEL12-2415 +Converter_DCDC:TEL12-2415WI +Converter_DCDC:TEL12-2422 +Converter_DCDC:TEL12-2422WI +Converter_DCDC:TEL12-2423 +Converter_DCDC:TEL12-2423WI +Converter_DCDC:TEL12-4811 +Converter_DCDC:TEL12-4811WI +Converter_DCDC:TEL12-4812 +Converter_DCDC:TEL12-4812WI +Converter_DCDC:TEL12-4813 +Converter_DCDC:TEL12-4813WI +Converter_DCDC:TEL12-4815 +Converter_DCDC:TEL12-4815WI +Converter_DCDC:TEL12-4822 +Converter_DCDC:TEL12-4822WI +Converter_DCDC:TEL12-4823 +Converter_DCDC:TEL12-4823WI +Converter_DCDC:TEN10-11010WIRH +Converter_DCDC:TEN10-11011WIRH +Converter_DCDC:TEN10-11012WIRH +Converter_DCDC:TEN10-11013WIRH +Converter_DCDC:TEN10-11015WIRH +Converter_DCDC:TEN10-11021WIRH +Converter_DCDC:TEN10-11022WIRH +Converter_DCDC:TEN10-11023WIRH +Converter_DCDC:TEN20-11011WIRH +Converter_DCDC:TEN20-11012WIRH +Converter_DCDC:TEN20-11013WIRH +Converter_DCDC:TEN20-11015WIRH +Converter_DCDC:TEN20-11021WIRH +Converter_DCDC:TEN20-11022WIRH +Converter_DCDC:TEN20-11023WIRH Converter_DCDC:TEN20-2410WIN Converter_DCDC:TEN20-2411WIN Converter_DCDC:TEN20-2412WIN @@ -4154,6 +4788,47 @@ Converter_DCDC:TEN20-4813WIN Converter_DCDC:TEN20-4821WIN Converter_DCDC:TEN20-4822WIN Converter_DCDC:TEN20-4823WIN +Converter_DCDC:TEN3-11010WIRH +Converter_DCDC:TEN3-11011WIRH +Converter_DCDC:TEN3-11012WIRH +Converter_DCDC:TEN3-11013WIRH +Converter_DCDC:TEN3-11015WIRH +Converter_DCDC:TEN3-11021WIRH +Converter_DCDC:TEN3-11022WIRH +Converter_DCDC:TEN3-11023WIRH +Converter_DCDC:TEN40-11011WIRH +Converter_DCDC:TEN40-11012WIRH +Converter_DCDC:TEN40-11013WIRH +Converter_DCDC:TEN40-11015WIRH +Converter_DCDC:TEN40-11022WIRH +Converter_DCDC:TEN40-11023WIRH +Converter_DCDC:TEN6-11010WIRH +Converter_DCDC:TEN6-11011WIRH +Converter_DCDC:TEN6-11012WIRH +Converter_DCDC:TEN6-11013WIRH +Converter_DCDC:TEN6-11015WIRH +Converter_DCDC:TEN6-11021WIRH +Converter_DCDC:TEN6-11022WIRH +Converter_DCDC:TEN6-11023WIRH +Converter_DCDC:THB10-1211 +Converter_DCDC:THB10-1212 +Converter_DCDC:THB10-1222 +Converter_DCDC:THB10-1223 +Converter_DCDC:THB10-2411 +Converter_DCDC:THB10-2412 +Converter_DCDC:THB10-2422 +Converter_DCDC:THB10-2423 +Converter_DCDC:THB10-4811 +Converter_DCDC:THB10-4812 +Converter_DCDC:THB10-4822 +Converter_DCDC:THB10-4823 +Converter_DCDC:THR40-7211WI +Converter_DCDC:THR40-7212WI +Converter_DCDC:THR40-7213WI +Converter_DCDC:THR40-72154WI +Converter_DCDC:THR40-7215WI +Converter_DCDC:THR40-7222WI +Converter_DCDC:THR40-7223WI Converter_DCDC:TMA-0505D Converter_DCDC:TMA-0505S Converter_DCDC:TMA-0512D @@ -4178,6 +4853,21 @@ Converter_DCDC:TMA-2412D Converter_DCDC:TMA-2412S Converter_DCDC:TMA-2415D Converter_DCDC:TMA-2415S +Converter_DCDC:TME-0303S +Converter_DCDC:TME-0305S +Converter_DCDC:TME-0503S +Converter_DCDC:TME-0505S +Converter_DCDC:TME-0509S +Converter_DCDC:TME-0512S +Converter_DCDC:TME-0515S +Converter_DCDC:TME-1205S +Converter_DCDC:TME-1209S +Converter_DCDC:TME-1212S +Converter_DCDC:TME-1215S +Converter_DCDC:TME-2405S +Converter_DCDC:TME-2409S +Converter_DCDC:TME-2412S +Converter_DCDC:TME-2415S Converter_DCDC:TMR-0510 Converter_DCDC:TMR-0511 Converter_DCDC:TMR-0512 @@ -4198,6 +4888,18 @@ Converter_DCDC:TMR2-4810WI Converter_DCDC:TMR2-4811WI Converter_DCDC:TMR2-4812WI Converter_DCDC:TMR2-4813WI +Converter_DCDC:TMR4-2411WI +Converter_DCDC:TMR4-2412WI +Converter_DCDC:TMR4-2413WI +Converter_DCDC:TMR4-2415WI +Converter_DCDC:TMR4-2422WI +Converter_DCDC:TMR4-2423WI +Converter_DCDC:TMR4-4811WI +Converter_DCDC:TMR4-4812WI +Converter_DCDC:TMR4-4813WI +Converter_DCDC:TMR4-4815WI +Converter_DCDC:TMR4-4822WI +Converter_DCDC:TMR4-4823WI Converter_DCDC:TMU3-0511 Converter_DCDC:TMU3-0512 Converter_DCDC:TMU3-0513 @@ -4207,9 +4909,25 @@ Converter_DCDC:TMU3-1213 Converter_DCDC:TMU3-2411 Converter_DCDC:TMU3-2412 Converter_DCDC:TMU3-2413 +Converter_DCDC:TPS43060RTE +Converter_DCDC:TPS54240DGQ +Converter_DCDC:TPS54240DRC +Converter_DCDC:TPS61022 Converter_DCDC:TPSM53602RDA Converter_DCDC:TPSM53603RDA Converter_DCDC:TPSM53604RDA +Converter_DCDC:TRA3-0511 +Converter_DCDC:TRA3-0512 +Converter_DCDC:TRA3-0513 +Converter_DCDC:TRA3-0519 +Converter_DCDC:TRA3-1211 +Converter_DCDC:TRA3-1212 +Converter_DCDC:TRA3-1213 +Converter_DCDC:TRA3-1219 +Converter_DCDC:TRA3-2411 +Converter_DCDC:TRA3-2412 +Converter_DCDC:TRA3-2413 +Converter_DCDC:TRA3-2419 Converter_DCDC:TRI1-0511 Converter_DCDC:TRI1-0512 Converter_DCDC:TRI1-0513 @@ -4252,6 +4970,7 @@ CPLD_Microchip:ATF1504AS-xAx44 CPLD_Microchip:ATF1504ASL-xAx44 CPLD_Microchip:ATF1504ASV-xAx44 CPLD_Microchip:ATF1504ASVL-xAx44 +CPLD_Renesas:SLG46826G CPLD_Xilinx:XC7336 CPLD_Xilinx:XC95108PC84 CPLD_Xilinx:XC95108PQ100 @@ -4324,6 +5043,11 @@ Device:Buzzer Device:C Device:C_45deg Device:C_Feedthrough +Device:C_Network04 +Device:C_Network05 +Device:C_Network06 +Device:C_Network07 +Device:C_Network08 Device:C_Polarized Device:C_Polarized_Series_2C Device:C_Polarized_Small @@ -4366,8 +5090,8 @@ Device:D_Bridge_-A+A Device:D_Bridge_-AA+ Device:D_Capacitance Device:D_Capacitance_Filled -Device:D_Constant_Current -Device:D_Constant_Current_Small +Device:D_Current-regulator +Device:D_Current-regulator_Small Device:D_Dual_CommonAnode_AKK Device:D_Dual_CommonAnode_AKK_Parallel Device:D_Dual_CommonAnode_AKK_Split @@ -4472,6 +5196,8 @@ Device:D_TVS_Dual_AAC Device:D_TVS_Dual_ACA Device:D_TVS_Dual_CAA Device:D_TVS_Filled +Device:D_TVS_Small +Device:D_TVS_Small_Filled Device:D_TemperatureDependent Device:D_TemperatureDependent_Filled Device:D_Tunnel @@ -4511,6 +5237,9 @@ Device:Filter_EMI_CommonMode Device:Filter_EMI_LCL Device:Filter_EMI_LL Device:Filter_EMI_LLL +Device:Filter_EMI_LLLL +Device:Filter_EMI_LLLL_15263748 +Device:Filter_EMI_LLL_162534 Device:Filter_EMI_LL_1423 Device:FrequencyCounter Device:Fuse @@ -4649,24 +5378,6 @@ Device:Oscilloscope Device:PeltierElement Device:Polyfuse Device:Polyfuse_Small -Device:Q_Dual_NMOS_G1S2G2D2S1D1 -Device:Q_Dual_NMOS_S1G1D2S2G2D1 -Device:Q_Dual_NMOS_S1G1S2G2D2D2D1D1 -Device:Q_Dual_NPN_C2C1E1E2 -Device:Q_Dual_NPN_NPN_BRT_E1B1C2E2B2C1 -Device:Q_Dual_NPN_NPN_C2E2C1E1B1B2 -Device:Q_Dual_NPN_NPN_E1B1C2E2B2C1 -Device:Q_Dual_NPN_PNP_BRT_E1B1C2E2B2C1 -Device:Q_Dual_NPN_PNP_E1B1C2E2B2C1 -Device:Q_Dual_PMOS_G1S2G2D2S1D1 -Device:Q_Dual_PMOS_S1G1D2S2G2D1 -Device:Q_Dual_PMOS_S1G1S2G2D2D2D1D1 -Device:Q_Dual_PNP_C2C1E1E2 -Device:Q_Dual_PNP_NPN_BRT_E1B1C2E2B2C1 -Device:Q_Dual_PNP_PNP_BRT_E1B1C2E2B2C1 -Device:Q_Dual_PNP_PNP_C1B1B2C2E2E1 -Device:Q_Dual_PNP_PNP_C2E2C1E1B1B2 -Device:Q_Dual_PNP_PNP_E1B1C2E2B2C1 Device:Q_NIGBT_CEG Device:Q_NIGBT_CGE Device:Q_NIGBT_ECG @@ -4681,38 +5392,13 @@ Device:Q_NJFET_GDS Device:Q_NJFET_GSD Device:Q_NJFET_SDG Device:Q_NJFET_SGD -Device:Q_NMOS_DGS -Device:Q_NMOS_DSG -Device:Q_NMOS_Depletion_DGS -Device:Q_NMOS_Depletion_DSG -Device:Q_NMOS_Depletion_GDS -Device:Q_NMOS_Depletion_GSD -Device:Q_NMOS_Depletion_SDG -Device:Q_NMOS_Depletion_SGD -Device:Q_NMOS_GDS -Device:Q_NMOS_GDSD -Device:Q_NMOS_GSD -Device:Q_NMOS_SDG -Device:Q_NMOS_SDGD -Device:Q_NMOS_SGD -Device:Q_NPN_BCE -Device:Q_NPN_BCEC -Device:Q_NPN_BEC -Device:Q_NPN_BEC_BRT -Device:Q_NPN_CBE -Device:Q_NPN_CEB -Device:Q_NPN_Darlington_BCE -Device:Q_NPN_Darlington_BCEC -Device:Q_NPN_Darlington_BEC -Device:Q_NPN_Darlington_CBE -Device:Q_NPN_Darlington_CEB -Device:Q_NPN_Darlington_EBC -Device:Q_NPN_Darlington_ECB -Device:Q_NPN_Darlington_ECBC -Device:Q_NPN_EBC -Device:Q_NPN_ECB -Device:Q_NPN_ECBC -Device:Q_NPN_ECB_BRT +Device:Q_NMOS +Device:Q_NMOS_Depletion +Device:Q_NPN +Device:Q_NPN_BRT +Device:Q_NPN_BRT_No_R2 +Device:Q_NPN_CurrentMirror +Device:Q_NPN_Darlington Device:Q_NUJT_BEB Device:Q_PJFET_DGS Device:Q_PJFET_DSG @@ -4720,32 +5406,13 @@ Device:Q_PJFET_GDS Device:Q_PJFET_GSD Device:Q_PJFET_SDG Device:Q_PJFET_SGD -Device:Q_PMOS_DGS -Device:Q_PMOS_DSG -Device:Q_PMOS_GDS -Device:Q_PMOS_GDSD -Device:Q_PMOS_GSD -Device:Q_PMOS_SDG -Device:Q_PMOS_SDGD -Device:Q_PMOS_SGD -Device:Q_PNP_BCE -Device:Q_PNP_BCEC -Device:Q_PNP_BEC -Device:Q_PNP_BEC_BRT -Device:Q_PNP_CBE -Device:Q_PNP_CEB -Device:Q_PNP_Darlington_BCE -Device:Q_PNP_Darlington_BCEC -Device:Q_PNP_Darlington_BEC -Device:Q_PNP_Darlington_CBE -Device:Q_PNP_Darlington_CEB -Device:Q_PNP_Darlington_EBC -Device:Q_PNP_Darlington_ECB -Device:Q_PNP_Darlington_ECBC -Device:Q_PNP_EBC -Device:Q_PNP_ECB -Device:Q_PNP_ECBC -Device:Q_PNP_ECB_BRT +Device:Q_PMOS +Device:Q_PMOS_Depletion +Device:Q_PNP +Device:Q_PNP_BRT +Device:Q_PNP_BRT_No_R2 +Device:Q_PNP_CurrentMirror +Device:Q_PNP_Darlington Device:Q_PUJT_BEB Device:Q_Photo_NPN Device:Q_Photo_NPN_CBE @@ -4758,12 +5425,7 @@ Device:Q_SCR_GAK Device:Q_SCR_GKA Device:Q_SCR_KAG Device:Q_SCR_KGA -Device:Q_TRIAC_A1A2G -Device:Q_TRIAC_A1GA2 -Device:Q_TRIAC_A2A1G -Device:Q_TRIAC_A2GA1 -Device:Q_TRIAC_GA1A2 -Device:Q_TRIAC_GA2A1 +Device:Q_Triac Device:R Device:RFShield_OnePiece Device:RFShield_TwoPieces @@ -4846,6 +5508,7 @@ Device:R_Pack11_Split Device:R_Photo Device:R_Potentiometer Device:R_Potentiometer_Dual +Device:R_Potentiometer_Dual_MountingPin Device:R_Potentiometer_Dual_Separate Device:R_Potentiometer_MountingPin Device:R_Potentiometer_Small @@ -4864,6 +5527,7 @@ Device:Resonator Device:Resonator_Small Device:RotaryEncoder Device:RotaryEncoder_Switch +Device:RotaryEncoder_Switch_MP Device:Solar_Cell Device:Solar_Cells Device:SparkGap @@ -4899,6 +5563,8 @@ Device:Voltmeter_AC Device:Voltmeter_DC Diode:1.5KExxA Diode:1.5KExxCA +Diode:1.5SMCxxA +Diode:1.5SMCxxCA Diode:1N4001 Diode:1N4002 Diode:1N4003 @@ -4917,6 +5583,11 @@ Diode:1N4448W Diode:1N4448WS Diode:1N4448WT Diode:1N47xxA +Diode:1N4933 +Diode:1N4934 +Diode:1N4935 +Diode:1N4936 +Diode:1N4937 Diode:1N53xxB Diode:1N5400 Diode:1N5401 @@ -4938,6 +5609,7 @@ Diode:1N5819WS Diode:1N5820 Diode:1N5821 Diode:1N5822 +Diode:1N5908 Diode:1N6263 Diode:1N62xxA Diode:1N62xxCA @@ -5005,6 +5677,7 @@ Diode:BAT54C Diode:BAT54CW Diode:BAT54J Diode:BAT54S +Diode:BAT54SDW Diode:BAT54SW Diode:BAT54W Diode:BAT60A @@ -5216,6 +5889,9 @@ Diode:ESD131-B1-W0201 Diode:ESD5Zxx Diode:ESD9B3.3ST5G Diode:ESD9B5.0ST5G +Diode:ESH2PB +Diode:ESH2PC +Diode:ESH2PD Diode:HN2D02FU Diode:IDDD04G65C6 Diode:IDDD06G65C6 @@ -5242,6 +5918,7 @@ Diode:MBR340 Diode:MBR735 Diode:MBR745 Diode:MBRA340 +Diode:MBRS340 Diode:MCL4148 Diode:MCL4448 Diode:MM3Zxx @@ -5353,6 +6030,16 @@ Diode:PMEG6045ETP Diode:PMEG60T10ELR Diode:PMEG60T20ELR Diode:PMEG60T30ELR +Diode:PTVS10VZ1USK +Diode:PTVS12VZ1USK +Diode:PTVS15VZ1USK +Diode:PTVS18VZ1USK +Diode:PTVS20VZ1USK +Diode:PTVS22VZ1USK +Diode:PTVS26VZ1USK +Diode:PTVS5V0Z1USK +Diode:PTVS5V0Z1USKP +Diode:PTVS7V5Z1USK Diode:Panasonic_MA5J002E Diode:RF01VM2S Diode:Rohm_UMN1N @@ -5371,6 +6058,12 @@ Diode:SD12_SOD323 Diode:SD15_SOD323 Diode:SD24_SOD323 Diode:SD36_SOD323 +Diode:SM15T36A +Diode:SM15T36CA +Diode:SM15T6V8A +Diode:SM15T6V8CA +Diode:SM15T7V5A +Diode:SM15T7V5CA Diode:SM2000 Diode:SM4001 Diode:SM4002 @@ -5379,9 +6072,15 @@ Diode:SM4004 Diode:SM4005 Diode:SM4006 Diode:SM4007 +Diode:SM5059 +Diode:SM5060 +Diode:SM5061 +Diode:SM5062 +Diode:SM5063 Diode:SM513 Diode:SM516 Diode:SM518 +Diode:SM5908 Diode:SM6T100A Diode:SM6T10A Diode:SM6T12A @@ -5546,6 +6245,33 @@ Diode:SMF8V0A Diode:SMF8V5A Diode:SMF9V0A Diode:SMZxxx +Diode:SS110 +Diode:SS1150 +Diode:SS12 +Diode:SS1200 +Diode:SS13 +Diode:SS14 +Diode:SS15 +Diode:SS16 +Diode:SS18 +Diode:SS210 +Diode:SS2150 +Diode:SS22 +Diode:SS2200 +Diode:SS23 +Diode:SS24 +Diode:SS25 +Diode:SS26 +Diode:SS28 +Diode:SS310 +Diode:SS3150 +Diode:SS32 +Diode:SS3200 +Diode:SS33 +Diode:SS34 +Diode:SS35 +Diode:SS36 +Diode:SS38 Diode:STBR3008WY Diode:STBR3012WY Diode:STBR6008WY @@ -5636,13 +6362,20 @@ Diode_Bridge:B80C5000-3x00A Diode_Bridge:B80C800DM Diode_Bridge:B80R Diode_Bridge:DF005M +Diode_Bridge:DF005S Diode_Bridge:DF01M +Diode_Bridge:DF01S Diode_Bridge:DF01S1 Diode_Bridge:DF02M +Diode_Bridge:DF02S Diode_Bridge:DF04M +Diode_Bridge:DF04S Diode_Bridge:DF06M +Diode_Bridge:DF06S Diode_Bridge:DF08M +Diode_Bridge:DF08S Diode_Bridge:DF10M +Diode_Bridge:DF10S Diode_Bridge:DMA40U1800GU Diode_Bridge:DNA40U2200GU Diode_Bridge:GBU4A @@ -5795,8 +6528,8 @@ Diode_Bridge:W04G Diode_Bridge:W06G Diode_Bridge:W08G Diode_Bridge:W10G -Diode_Laser:PL450B Diode_Laser:PL520 +Diode_Laser:PLT5_450B Diode_Laser:PLT5_488 Diode_Laser:PLT5_510 Diode_Laser:SPL_PL90 @@ -5903,6 +6636,7 @@ Display_Character:MAN73A Display_Character:MAN74A Display_Character:NHD-0420H1Z Display_Character:NHD-C0220BIZ +Display_Character:NHD-C0220BIZ-FSRGB Display_Character:RC1602A Display_Character:RC1602A-GHW-ESX Display_Character:SA15-11EWA @@ -5988,9 +6722,16 @@ Driver_FET:ACPL-P343 Driver_FET:ACPL-W343 Driver_FET:AN34092B Driver_FET:BSP75N +Driver_FET:BSP76 +Driver_FET:BTS4140N Driver_FET:EL7202CN +Driver_FET:EL7202CS Driver_FET:EL7212CN +Driver_FET:EL7212CS Driver_FET:EL7222CN +Driver_FET:EL7222CS +Driver_FET:FAN3111C +Driver_FET:FAN3111E Driver_FET:FAN3268 Driver_FET:FAN3278 Driver_FET:FAN7371 @@ -6117,6 +6858,9 @@ Driver_FET:ITS711L1 Driver_FET:ITS716G Driver_FET:ITS724G Driver_FET:L6491 +Driver_FET:LF2190N +Driver_FET:LM2105D +Driver_FET:LM2105DSG Driver_FET:LM5109AMA Driver_FET:LM5109ASD Driver_FET:LM5109BMA @@ -6164,6 +6908,9 @@ Driver_FET:STGAP2SCM Driver_FET:STGAP2SM Driver_FET:TC4421 Driver_FET:TC4422 +Driver_FET:TC4426xOA +Driver_FET:TC4427xOA +Driver_FET:TC4428xOA Driver_FET:TLP250 Driver_FET:UCC21520ADW Driver_FET:UCC21520DW @@ -6179,7 +6926,12 @@ Driver_Haptic:DRV2510-Q1 Driver_Haptic:DRV2605LDGS Driver_LED:AL8860MP Driver_LED:AL8860WT +Driver_LED:AP3019AKTR +Driver_LED:AP3019AKTTR Driver_LED:BCR430UW6 +Driver_LED:CH455G +Driver_LED:CH455H +Driver_LED:CH455K Driver_LED:CL220K4-G Driver_LED:CL220N5-G Driver_LED:DIO5661CD6 @@ -6202,6 +6954,7 @@ Driver_LED:IS31FL3216A Driver_LED:IS31FL3218-GR Driver_LED:IS31FL3218-QF Driver_LED:IS31FL3236-TQ +Driver_LED:IS31FL3236A-TQ Driver_LED:IS31FL3731-QF Driver_LED:IS31FL3731-SA Driver_LED:IS31FL3733-QF @@ -6209,6 +6962,9 @@ Driver_LED:IS31FL3733-TQ Driver_LED:IS31FL3736 Driver_LED:IS31FL3737 Driver_LED:IS31LT3360 +Driver_LED:KTD2026 +Driver_LED:KTD2027 +Driver_LED:KTD2061xxUAC Driver_LED:LED1642GWPTR Driver_LED:LED1642GWQTR Driver_LED:LED1642GWTTR @@ -6217,6 +6973,7 @@ Driver_LED:LED5000 Driver_LED:LM3914N Driver_LED:LM3914V Driver_LED:LP5036 +Driver_LED:LP8868XQDMT Driver_LED:LT3465 Driver_LED:LT3465A Driver_LED:LT3755xMSE @@ -6238,22 +6995,38 @@ Driver_LED:MAX7221xRG Driver_LED:MAX7221xWG Driver_LED:MBI5252GFN Driver_LED:MBI5252GP +Driver_LED:MC14495P Driver_LED:MCP1643xMS Driver_LED:MCP1662-xOT +Driver_LED:MP3362GJ Driver_LED:MPQ2483DQ +Driver_LED:MPQ3362GJ-AEC1 Driver_LED:NCP5623DTBR2G Driver_LED:NCR401U Driver_LED:PCA9531PW Driver_LED:PCA9635 Driver_LED:PCA9685BS Driver_LED:PCA9685PW +Driver_LED:PCA9745BTW +Driver_LED:RCD-24 Driver_LED:ST1CC40DR Driver_LED:ST1CC40PUR -Driver_LED:STP08CP05 +Driver_LED:STP08CP05B +Driver_LED:STP08CP05M +Driver_LED:STP08CP05T Driver_LED:STP08CP05XT -Driver_LED:STP16CP05 +Driver_LED:STP16CP05M +Driver_LED:STP16CP05P +Driver_LED:STP16CP05T Driver_LED:STP16CP05XT +Driver_LED:STP16CPC26M +Driver_LED:STP16CPC26P +Driver_LED:STP16CPC26T +Driver_LED:STP16CPC26X +Driver_LED:TCA6507RUE Driver_LED:TLC59108xPW +Driver_LED:TLC5916 +Driver_LED:TLC5917 Driver_LED:TLC5940NT Driver_LED:TLC5940PWP Driver_LED:TLC5947DAP @@ -6266,15 +7039,21 @@ Driver_LED:TLC5957RTQ Driver_LED:TLC5971PWP Driver_LED:TLC5971RGE Driver_LED:TLC5973 +Driver_LED:TPS61165DBV Driver_LED:TPS92692PWP Driver_LED:WS2811 +Driver_LED:iC-HTG Driver_Motor:A4950E Driver_Motor:A4950K Driver_Motor:A4952_LY Driver_Motor:A4953_LJ Driver_Motor:A4954 Driver_Motor:AMT49413 +Driver_Motor:DRV8212P Driver_Motor:DRV8308 +Driver_Motor:DRV8412 +Driver_Motor:DRV8432 +Driver_Motor:DRV8461SPWP Driver_Motor:DRV8662 Driver_Motor:DRV8711 Driver_Motor:DRV8800PWP @@ -6295,6 +7074,10 @@ Driver_Motor:DRV8848 Driver_Motor:DRV8870DDA Driver_Motor:DRV8871DDA Driver_Motor:DRV8872DDA +Driver_Motor:EMC2301-x-ACZL +Driver_Motor:EMC2302-x-AIZL +Driver_Motor:EMC2303-x-KP +Driver_Motor:EMC2305-x-AP Driver_Motor:L293 Driver_Motor:L293D Driver_Motor:L293E @@ -6303,6 +7086,11 @@ Driver_Motor:L298HN Driver_Motor:L298N Driver_Motor:L298P Driver_Motor:LMD18200 +Driver_Motor:MAX22201 +Driver_Motor:MAX22202 +Driver_Motor:MAX22207 +Driver_Motor:MP6536DU +Driver_Motor:PAC5527QM Driver_Motor:PG001M Driver_Motor:Pololu_Breakout_A4988 Driver_Motor:Pololu_Breakout_DRV8825 @@ -6324,13 +7112,19 @@ Driver_Motor:STSPIN230 Driver_Motor:STSPIN233 Driver_Motor:STSPIN240 Driver_Motor:TB6612FNG +Driver_Motor:TC78H670FTG Driver_Motor:TMC2041-LA Driver_Motor:TMC2100-LA Driver_Motor:TMC2100-TA Driver_Motor:TMC2130-LA Driver_Motor:TMC2130-TA Driver_Motor:TMC2160 +Driver_Motor:TMC2202-WA +Driver_Motor:TMC2208-LA +Driver_Motor:TMC2224-LA +Driver_Motor:TMC2226-SA Driver_Motor:TMC262 +Driver_Motor:TMC2660 Driver_Motor:TMC5130A-TA Driver_Motor:TMC5160A-TA Driver_Motor:VNH2SP30 @@ -6373,6 +7167,11 @@ DSP_Motorola:DSP56301 DSP_Texas:TMS320LF2406PZ Fiber_Optic:AFBR-1624Z Fiber_Optic:AFBR-2624Z +Filter:0603USB-142 +Filter:0603USB-222 +Filter:0603USB-251 +Filter:0603USB-601 +Filter:0603USB-951 Filter:0850BM14E0016 Filter:0900FM15K0039 Filter:1FP41-4R @@ -6387,12 +7186,26 @@ Filter:1FP65-0R Filter:1FP65-1R Filter:B39162B8813P810 Filter:BNX025 +Filter:Choke_CommonMode_FerriteCore_1234 +Filter:Choke_CommonMode_FerriteCore_1243 +Filter:Choke_CommonMode_FerriteCore_1324 +Filter:Choke_CommonMode_FerriteCore_1342 +Filter:Choke_CommonMode_FerriteCore_1423 +Filter:Choke_CommonMode_PulseElectronics_PH9455x105NL +Filter:Choke_CommonMode_PulseElectronics_PH9455x155NL +Filter:Choke_CommonMode_PulseElectronics_PH9455x156NL +Filter:Choke_CommonMode_PulseElectronics_PH9455x205NL +Filter:Choke_CommonMode_PulseElectronics_PH9455x356NL +Filter:Choke_CommonMode_PulseElectronics_PH9455x405NL +Filter:Choke_CommonMode_PulseElectronics_PH9455x705NL +Filter:Choke_CommonMode_PulseElectronics_PH9455x826NL Filter:Choke_Schaffner_RN102-0.3-02-12M Filter:Choke_Schaffner_RN102-0.3-02-22M Filter:Choke_Schaffner_RN102-0.6-02-4M4 Filter:Choke_Schaffner_RN102-1-02-3M0 Filter:Choke_Schaffner_RN102-1.5-02-1M6 Filter:Choke_Schaffner_RN102-2-02-1M1 +Filter:Choke_Wurth_WE-CNSW_744232090 Filter:FN405-0.5-02 Filter:FN405-1-02 Filter:FN405-10-02 @@ -6410,6 +7223,12 @@ Filter:FN406B-6-02 Filter:FN406B-8.4-02 Filter:LTC1562xG-2 Filter:LTC1562xxG +Filter:P300PL104M275xC222 +Filter:P300PL104M275xC332 +Filter:P300PL104M275xC472 +Filter:P300PL154M275xC222 +Filter:P300PL154M275xC332 +Filter:P300PL154M275xC472 Filter:SAFFA1G58KA0F0A Filter:SAFFA1G96FN0F0A Filter:SAFFA2G14FA0F0A @@ -6749,6 +7568,7 @@ Interface:AD9851 Interface:AD9910 Interface:AD9912 Interface:AD9951 +Interface:AD9954 Interface:AM26LS31CD Interface:AM26LS31CDB Interface:AM26LS31CN @@ -6807,6 +7627,8 @@ Interface:PCA9615DP Interface:PCI9030-PQFP176 Interface:S5933_PQ160 Interface:SI9986 +Interface:SLB9660xT +Interface:SLB9665xT Interface:SN65LVDS047D Interface:SN65LVDS047PW Interface:SN65LVDS1D @@ -6841,7 +7663,10 @@ Interface:Z8420 Interface:Z84C20 Interface_CAN_LIN:ADM3053 Interface_CAN_LIN:ADM3057ExRW +Interface_CAN_LIN:CA-IF1042LVS +Interface_CAN_LIN:ISO1044BD Interface_CAN_LIN:ISO1050DUB +Interface_CAN_LIN:ISOW1044 Interface_CAN_LIN:LTC2875-DD Interface_CAN_LIN:LTC2875-S8 Interface_CAN_LIN:MCP2021A-xxxxMD @@ -6860,6 +7685,9 @@ Interface_CAN_LIN:MCP2515-xSO Interface_CAN_LIN:MCP2515-xST Interface_CAN_LIN:MCP2517FD-xJHA Interface_CAN_LIN:MCP2517FD-xSL +Interface_CAN_LIN:MCP251863T-E-9PX +Interface_CAN_LIN:MCP251863T-H-SS +Interface_CAN_LIN:MCP2518FD-xQBB Interface_CAN_LIN:MCP2542FDxMF Interface_CAN_LIN:MCP2542WFDxMF Interface_CAN_LIN:MCP2551-I-P @@ -6903,6 +7731,8 @@ Interface_CAN_LIN:TCAN334 Interface_CAN_LIN:TCAN334G Interface_CAN_LIN:TCAN337 Interface_CAN_LIN:TCAN337G +Interface_CAN_LIN:TCAN4550RGY +Interface_CAN_LIN:TCAN4551RGYRQ1 Interface_CAN_LIN:TJA1021T Interface_CAN_LIN:TJA1021TK Interface_CAN_LIN:TJA1029T @@ -6948,6 +7778,8 @@ Interface_Ethernet:LAN7500-ABJZ Interface_Ethernet:LAN8710A Interface_Ethernet:LAN8720A Interface_Ethernet:LAN8742A +Interface_Ethernet:LAN9303 +Interface_Ethernet:LAN9303i Interface_Ethernet:LAN9512 Interface_Ethernet:LAN9512i Interface_Ethernet:LAN9513 @@ -6957,13 +7789,23 @@ Interface_Ethernet:LAN9514i Interface_Ethernet:RTL8211EG-VB-CG Interface_Ethernet:VSC8541XMV-0x Interface_Ethernet:W5100 +Interface_Ethernet:W5100S-L +Interface_Ethernet:W5100S-Q Interface_Ethernet:W5500 +Interface_Ethernet:W6100-L +Interface_Ethernet:W6100-Q Interface_Ethernet:WGI210AT Interface_Expansion:AS1115-BQFT Interface_Expansion:AS1115-BSST +Interface_Expansion:AW9523B Interface_Expansion:LTC4314xGN Interface_Expansion:LTC4314xUDC +Interface_Expansion:LTC4316xDD Interface_Expansion:LTC4317 +Interface_Expansion:MAX31910xUI +Interface_Expansion:MAX31911xUI +Interface_Expansion:MAX31912xUI +Interface_Expansion:MAX31913xUI Interface_Expansion:MAX7325AEG+ Interface_Expansion:MCP23008-xML Interface_Expansion:MCP23008-xP @@ -6978,6 +7820,7 @@ Interface_Expansion:MCP23S17_SO Interface_Expansion:MCP23S17_SP Interface_Expansion:MCP23S17_SS Interface_Expansion:P82B96 +Interface_Expansion:PCA9506BS Interface_Expansion:PCA9516 Interface_Expansion:PCA9536D Interface_Expansion:PCA9536DP @@ -6997,12 +7840,17 @@ Interface_Expansion:PCA9555PW Interface_Expansion:PCA9557BS Interface_Expansion:PCA9557D Interface_Expansion:PCA9557PW +Interface_Expansion:PCA9847PW +Interface_Expansion:PCAL6416AHF Interface_Expansion:PCAL6416APW Interface_Expansion:PCAL6534EV -Interface_Expansion:PCF8574 -Interface_Expansion:PCF8574A +Interface_Expansion:PCF8574AP +Interface_Expansion:PCF8574AT Interface_Expansion:PCF8574ATS +Interface_Expansion:PCF8574P +Interface_Expansion:PCF8574T Interface_Expansion:PCF8574TS +Interface_Expansion:PCF8575DBR Interface_Expansion:PCF8584 Interface_Expansion:PCF8591 Interface_Expansion:STMPE1600 @@ -7027,6 +7875,10 @@ Interface_Expansion:TCA9555PWR Interface_Expansion:TCA9555RGER Interface_Expansion:TCA9555RTWR Interface_Expansion:TPIC6595 +Interface_Expansion:XRA1201IG24 +Interface_Expansion:XRA1201IL24 +Interface_Expansion:XRA1201PIG24 +Interface_Expansion:XRA1201PIL24 Interface_HDMI:ADV7611 Interface_HDMI:TPD12S520DBT Interface_HID:JoyWarrior24A10L @@ -7050,6 +7902,7 @@ Interface_LineDriver:UA9638CDR Interface_LineDriver:UA9638CDRG4 Interface_LineDriver:UA9638CP Interface_LineDriver:UA9638CPE4 +Interface_Optical:IRM-H6xxT Interface_Optical:IS471F Interface_Optical:IS485 Interface_Optical:IS486 @@ -7102,6 +7955,13 @@ Interface_UART:8250 Interface_UART:8252 Interface_UART:ADM101E Interface_UART:ADM1491EBR +Interface_UART:ADM205 +Interface_UART:ADM206 +Interface_UART:ADM207 +Interface_UART:ADM208 +Interface_UART:ADM209 +Interface_UART:ADM211 +Interface_UART:ADM213 Interface_UART:ADM222 Interface_UART:ADM232A Interface_UART:ADM242 @@ -7126,6 +7986,9 @@ Interface_UART:GD75232DW Interface_UART:GD75232N Interface_UART:GD75232PW Interface_UART:ICL3232 +Interface_UART:ISL3172E +Interface_UART:ISL3175E +Interface_UART:ISL3178E Interface_UART:ISL3280ExHZ Interface_UART:ISL3281ExHZ Interface_UART:ISL3282ExRHZ @@ -7178,6 +8041,8 @@ Interface_UART:MAX1487E Interface_UART:MAX202 Interface_UART:MAX232 Interface_UART:MAX232I +Interface_UART:MAX238xNG+ +Interface_UART:MAX238xWG+ Interface_UART:MAX3051 Interface_UART:MAX3072E Interface_UART:MAX3075E @@ -7233,20 +8098,37 @@ Interface_UART:SP3485CN Interface_UART:SP3485CP Interface_UART:SP3485EN Interface_UART:SP3485EP -Interface_UART:ST485EBDR +Interface_UART:SSP3085 +Interface_UART:ST202ExD +Interface_UART:ST232ExD +Interface_UART:ST485E Interface_UART:THVD1400D Interface_UART:THVD1420D +Interface_UART:THVD1450D +Interface_UART:THVD1450DR Interface_UART:THVD1451D +Interface_UART:THVD1500 +Interface_UART:THVD8000 Interface_UART:Z8530 Interface_USB:ADUM3160 Interface_USB:ADUM4160 +Interface_USB:AP33771 Interface_USB:BQ24392 +Interface_USB:CH224K +Interface_USB:CH236D +Interface_USB:CH246D Interface_USB:CH330N +Interface_USB:CH334R Interface_USB:CH340C Interface_USB:CH340E Interface_USB:CH340G +Interface_USB:CH340K +Interface_USB:CH340N Interface_USB:CH340T Interface_USB:CH340X +Interface_USB:CH343G +Interface_USB:CH343P +Interface_USB:CH344Q Interface_USB:CH9102F Interface_USB:CP2102N-Axx-xQFN20 Interface_USB:CP2102N-Axx-xQFN24 @@ -7263,6 +8145,10 @@ Interface_USB:CY7C65213A-28PVXI Interface_USB:CY7C65213A-32LTXI Interface_USB:CY7C65215-32LTXI Interface_USB:CY7C65215A-32LTXI +Interface_USB:CYPD3171-24LQXQ +Interface_USB:CYPD3174-16SXQ +Interface_USB:CYPD3174-24LQXQ +Interface_USB:CYPD3175-24LQXQ Interface_USB:CYPD3177-24LQ Interface_USB:FE1.1s Interface_USB:FSUSB30MUX @@ -7284,6 +8170,7 @@ Interface_USB:FT231XS Interface_USB:FT232BM Interface_USB:FT232H Interface_USB:FT232RL +Interface_USB:FT234XD Interface_USB:FT240XQ Interface_USB:FT240XS Interface_USB:FT245BM @@ -7294,6 +8181,7 @@ Interface_USB:FUSB302B01MPX Interface_USB:FUSB302B10MPX Interface_USB:FUSB302B11MPX Interface_USB:FUSB302BMPX +Interface_USB:FUSB303BTMX Interface_USB:FUSB307BMPX Interface_USB:IP2721 Interface_USB:MA8601 @@ -7312,6 +8200,7 @@ Interface_USB:MCP2221AxML Interface_USB:MCP2221AxP Interface_USB:MCP2221AxSL Interface_USB:MCP2221AxST +Interface_USB:MP5034GJ Interface_USB:STULPI01A Interface_USB:STULPI01B Interface_USB:STUSB4500QTR @@ -7323,6 +8212,11 @@ Interface_USB:TPS2514 Interface_USB:TPS2514A Interface_USB:TPS2560 Interface_USB:TPS2561 +Interface_USB:TPS25730D +Interface_USB:TS3USB30EDGSR +Interface_USB:TS3USB30ERSWR +Interface_USB:TS3USBCA410 +Interface_USB:TS3USBCA420 Interface_USB:TUSB2036 Interface_USB:TUSB320 Interface_USB:TUSB320I @@ -7331,6 +8225,8 @@ Interface_USB:TUSB322I Interface_USB:TUSB4041I Interface_USB:TUSB7340 Interface_USB:TUSB8041 +Interface_USB:UPD720202K8-7x1-BAA +Interface_USB:USB2504 Interface_USB:USB2514B_Bi Interface_USB:USB3250-ABZJ Interface_USB:USB3300-EZK @@ -7340,6 +8236,7 @@ Interface_USB:USB3346 Interface_USB:USB3347 Interface_USB:USB3740B-AI2 Interface_USB:USB3740B-AI9 +Interface_USB:XR21B1424 Isolator:4N25 Isolator:4N26 Isolator:4N27 @@ -7374,6 +8271,7 @@ Isolator:ADuM120N Isolator:ADuM121N Isolator:ADuM1250 Isolator:ADuM1281 +Isolator:ADuM1300xRW Isolator:ADuM1400xRW Isolator:ADuM1401xRW Isolator:ADuM1402xRW @@ -7387,6 +8285,7 @@ Isolator:ADuM263N Isolator:ADuM3151 Isolator:ADuM3152 Isolator:ADuM3153 +Isolator:ADuM5211 Isolator:ADuM5401 Isolator:ADuM5402 Isolator:ADuM5403 @@ -7458,6 +8357,10 @@ Isolator:ISO1541 Isolator:ISO1642DWR Isolator:ISO1643DWR Isolator:ISO1644DWR +Isolator:ISO6731 +Isolator:ISO6740 +Isolator:ISO6741 +Isolator:ISO6742 Isolator:ISO7320C Isolator:ISO7320FC Isolator:ISO7321C @@ -7488,6 +8391,11 @@ Isolator:ISO7763DBQ Isolator:ISO7763DW Isolator:ISO7763FDBQ Isolator:ISO7763FDW +Isolator:ISOW7740 +Isolator:ISOW7741 +Isolator:ISOW7742 +Isolator:ISOW7743 +Isolator:ISOW7744 Isolator:LTV-247 Isolator:LTV-352T Isolator:LTV-354T @@ -7510,6 +8418,11 @@ Isolator:LTV-847S Isolator:MAX14850AEE+ Isolator:MAX14850ASE+ Isolator:MID400 +Isolator:MOCD207M +Isolator:MOCD208M +Isolator:MOCD211M +Isolator:MOCD213M +Isolator:MOCD217M Isolator:NSL-32 Isolator:PC3H4 Isolator:PC3H4A @@ -7599,6 +8512,66 @@ Isolator:Si8645BB-B-IS1 Isolator:Si8645BB-B-IU Isolator:Si8645BC-B-IS1 Isolator:Si8645BD-B-IS +Isolator:Si8660BA-AS1 +Isolator:Si8660BA-B-IS1 +Isolator:Si8660BB-AS1 +Isolator:Si8660BB-AU +Isolator:Si8660BB-B-IS1 +Isolator:Si8660BB-B-IU +Isolator:Si8660BC-AS1 +Isolator:Si8660BC-B-IS1 +Isolator:Si8660BD-AS +Isolator:Si8660BD-B-IS +Isolator:Si8660EB-AU +Isolator:Si8660EB-B-IU +Isolator:Si8660EC-AS1 +Isolator:Si8660EC-B-IS1 +Isolator:Si8660ED-AS +Isolator:Si8660ED-B-IS +Isolator:Si8661BB-AS1 +Isolator:Si8661BB-AU +Isolator:Si8661BB-B-IS1 +Isolator:Si8661BB-B-IU +Isolator:Si8661BC-AS1 +Isolator:Si8661BC-B-IS1 +Isolator:Si8661BD-AS +Isolator:Si8661BD-AS2 +Isolator:Si8661BD-B-IS +Isolator:Si8661BD-B-IS2 +Isolator:Si8661EB-AU +Isolator:Si8661EB-B-IU +Isolator:Si8661EC-AS1 +Isolator:Si8661EC-B-IS1 +Isolator:Si8661ED-AS +Isolator:Si8661ED-B-IS +Isolator:Si8662BB-AS1 +Isolator:Si8662BB-AU +Isolator:Si8662BB-B-IS1 +Isolator:Si8662BB-B-IU +Isolator:Si8662BC-AS1 +Isolator:Si8662BC-B-IS1 +Isolator:Si8662BD-AS +Isolator:Si8662BD-B-IS +Isolator:Si8662EB-AU +Isolator:Si8662EB-B-IU +Isolator:Si8662EC-AS1 +Isolator:Si8662EC-B-IS1 +Isolator:Si8662ED-AS +Isolator:Si8662ED-B-IS +Isolator:Si8663BB-AS1 +Isolator:Si8663BB-AU +Isolator:Si8663BB-B-IS1 +Isolator:Si8663BB-B-IU +Isolator:Si8663BC-AS1 +Isolator:Si8663BC-B-IS1 +Isolator:Si8663BD-AS +Isolator:Si8663BD-B-IS +Isolator:Si8663EB-AU +Isolator:Si8663EB-B-IU +Isolator:Si8663EC-AS1 +Isolator:Si8663EC-B-IS1 +Isolator:Si8663ED-AS +Isolator:Si8663ED-B-IS Isolator:TCMT1100 Isolator:TCMT1101 Isolator:TCMT1102 @@ -7623,6 +8596,7 @@ Isolator:TLP184 Isolator:TLP184xSE Isolator:TLP185 Isolator:TLP185xSE +Isolator:TLP2310 Isolator:TLP2703 Isolator:TLP2745 Isolator:TLP2748 @@ -7634,6 +8608,10 @@ Isolator:TLP290 Isolator:TLP290-4 Isolator:TLP291 Isolator:TLP291-4 +Isolator:TLP292 +Isolator:TLP292-4 +Isolator:TLP293 +Isolator:TLP293-4 Isolator:TLP3021 Isolator:TLP3022 Isolator:TLP3023 @@ -7663,6 +8641,8 @@ Isolator:VO615A-6 Isolator:VO615A-7 Isolator:VO615A-8 Isolator:VO615A-9 +Isolator:VOA300 +Isolator:VOS618A Isolator:VTL5C Isolator:VTL5Cx2 Isolator:ฯ€120U30 @@ -7673,9 +8653,15 @@ Isolator_Analog:ACPL-C79B Isolator_Analog:ACPL-C870 Isolator_Analog:ACPL-C87A Isolator_Analog:ACPL-C87B +Isolator_Analog:AMC3330 Isolator_Analog:IL300 +Isolator_Analog:LOC112 +Isolator_Analog:LOC112P +Isolator_Analog:LOC112S Jumper:Jumper_2_Bridged Jumper:Jumper_2_Open +Jumper:Jumper_2_Small_Bridged +Jumper:Jumper_2_Small_Open Jumper:Jumper_3_Bridged12 Jumper:Jumper_3_Open Jumper:SolderJumper_2_Bridged @@ -7693,6 +8679,7 @@ LED:ASMT-YTB7-0AA02 LED:ASMT-YTC2-0AA02 LED:CLS6B-FKW LED:CLV1L-FKB +LED:CLX6F-FKC LED:CQY99 LED:HDSP-4830 LED:HDSP-4830_2 @@ -7709,6 +8696,7 @@ LED:HLCP-J100_2 LED:IR204A LED:IR26-21C_L110_TR8 LED:IRL81A +LED:Inolux_IN-P55TATRGB LED:Inolux_IN-PI554FCH LED:Inolux_IN-PI556FCH LED:LD271 @@ -7717,6 +8705,8 @@ LED:LED_Cree_XHP50_12V LED:LED_Cree_XHP50_6V LED:LED_Cree_XHP70_12V LED:LED_Cree_XHP70_6V +LED:LTST-C235KGKRKT +LED:LiteOn_LTST-E563C LED:NeoPixel_THT LED:QLS6A-FKW LED:QLS6B-FKW @@ -7734,6 +8724,7 @@ LED:SMLVN6RGB LED:TSAL4400 LED:WS2812 LED:WS2812B +LED:WS2812B-2020 LED:WS2812S LED:WS2813 LED:WS2822S @@ -7747,13 +8738,23 @@ Logic_LevelTranslator:NLSV2T244MU Logic_LevelTranslator:SN74AUP1T34DCK Logic_LevelTranslator:SN74AVC4T245PW Logic_LevelTranslator:SN74AVC8T245PW +Logic_LevelTranslator:SN74LV1T125DBV +Logic_LevelTranslator:SN74LV1T125DCK Logic_LevelTranslator:SN74LV1T34DBV Logic_LevelTranslator:SN74LV1T34DCK Logic_LevelTranslator:SN74LVC1T45DBV Logic_LevelTranslator:SN74LVC1T45DCK Logic_LevelTranslator:SN74LVC1T45DRL +Logic_LevelTranslator:SN74LVC245APW Logic_LevelTranslator:SN74LVC2T45DCUR Logic_LevelTranslator:SN74LVC2T45YZP +Logic_LevelTranslator:SN74LVC8T245 +Logic_LevelTranslator:TCA9517ADGK +Logic_LevelTranslator:TCA9517D +Logic_LevelTranslator:TXB0101DBV +Logic_LevelTranslator:TXB0101DCK +Logic_LevelTranslator:TXB0101DRL +Logic_LevelTranslator:TXB0101YZP Logic_LevelTranslator:TXB0102DCT Logic_LevelTranslator:TXB0102DCU Logic_LevelTranslator:TXB0102YZP @@ -7763,7 +8764,11 @@ Logic_LevelTranslator:TXB0104RGY Logic_LevelTranslator:TXB0104RUT Logic_LevelTranslator:TXB0104YZT Logic_LevelTranslator:TXB0104ZXU +Logic_LevelTranslator:TXB0106PW +Logic_LevelTranslator:TXB0106RGY Logic_LevelTranslator:TXB0108DQSR +Logic_LevelTranslator:TXB0108PW +Logic_LevelTranslator:TXB0108RGY Logic_LevelTranslator:TXB0304RUT Logic_LevelTranslator:TXBN0304RUT Logic_LevelTranslator:TXS0101DBV @@ -7774,15 +8779,21 @@ Logic_LevelTranslator:TXS0102DCT Logic_LevelTranslator:TXS0102DCU Logic_LevelTranslator:TXS0102DQE Logic_LevelTranslator:TXS0102YZP +Logic_LevelTranslator:TXS0104ED +Logic_LevelTranslator:TXS0104EPW Logic_LevelTranslator:TXS0108EPW +Logic_LevelTranslator:TXS02612RTW Logic_Programmable:GAL16V8 Logic_Programmable:PAL16L8 Logic_Programmable:PAL20 Logic_Programmable:PAL20L8 Logic_Programmable:PAL20RS10 Logic_Programmable:PAL24 +Logic_Programmable:PEEL22CV10AP +Logic_Programmable:PEEL22CV10AS MCU_AnalogDevices:ADUC816 MCU_AnalogDevices:MAX32660GTP +MCU_AnalogDevices:MAX32670GTL MCU_Cypress:CY7C68013A-56LTX MCU_Cypress:CY7C68013A-56PVX MCU_Cypress:CY7C68014A-56LTX @@ -7821,6 +8832,10 @@ MCU_Cypress:CYBL10563-68FNXIT MCU_Cypress:CYBL10x6x-56LQxx MCU_Dialog:DA14691 MCU_Dialog:DA14695 +MCU_Espressif:ESP32-C3 +MCU_Espressif:ESP32-PICO-D4 +MCU_Espressif:ESP32-S2 +MCU_Espressif:ESP32-S3 MCU_Espressif:ESP8266EX MCU_Intel:80186 MCU_Intel:80188 @@ -7848,6 +8863,11 @@ MCU_Intel:IA186XLPLC68IR2 MCU_Intel:IA188XLPLC68IR2 MCU_Intel:M80C186 MCU_Intel:M80C186XL +MCU_Intel:P8031AH +MCU_Intel:P8051AH +MCU_Intel:P8052AH +MCU_Intel:P8751BH +MCU_Intel:P8752BH MCU_Microchip_8051:AT89C2051-12P MCU_Microchip_8051:AT89C2051-12S MCU_Microchip_8051:AT89C2051-24P @@ -7860,6 +8880,9 @@ MCU_Microchip_8051:AT89S2051-24P MCU_Microchip_8051:AT89S2051-24S MCU_Microchip_8051:AT89S4051-24P MCU_Microchip_8051:AT89S4051-24S +MCU_Microchip_8051:AT89x51xxA +MCU_Microchip_8051:AT89x51xxJ +MCU_Microchip_8051:AT89x51xxP MCU_Microchip_ATmega:ATmega128-16A MCU_Microchip_ATmega:ATmega128-16M MCU_Microchip_ATmega:ATmega1280-16A @@ -8616,120 +9639,120 @@ MCU_Microchip_PIC10:PIC10F320-IP MCU_Microchip_PIC10:PIC10F322-IMC MCU_Microchip_PIC10:PIC10F322-IOT MCU_Microchip_PIC10:PIC10F322-IP -MCU_Microchip_PIC12:PIC12C508-IJW -MCU_Microchip_PIC12:PIC12C508-IP -MCU_Microchip_PIC12:PIC12C508-ISM -MCU_Microchip_PIC12:PIC12C508A-IJW -MCU_Microchip_PIC12:PIC12C508A-IP -MCU_Microchip_PIC12:PIC12C508A-ISM -MCU_Microchip_PIC12:PIC12C508A-ISN -MCU_Microchip_PIC12:PIC12C509-IJW -MCU_Microchip_PIC12:PIC12C509-IP -MCU_Microchip_PIC12:PIC12C509-ISM -MCU_Microchip_PIC12:PIC12C509A-IJW -MCU_Microchip_PIC12:PIC12C509A-IP -MCU_Microchip_PIC12:PIC12C509A-ISM -MCU_Microchip_PIC12:PIC12C509A-ISN -MCU_Microchip_PIC12:PIC12C671-IJW -MCU_Microchip_PIC12:PIC12C671-IP -MCU_Microchip_PIC12:PIC12C671-ISN -MCU_Microchip_PIC12:PIC12C672-IJW -MCU_Microchip_PIC12:PIC12C672-IP -MCU_Microchip_PIC12:PIC12C672-ISN -MCU_Microchip_PIC12:PIC12CE518-IJW -MCU_Microchip_PIC12:PIC12CE518-IP -MCU_Microchip_PIC12:PIC12CE518-ISM -MCU_Microchip_PIC12:PIC12CE518-ISN -MCU_Microchip_PIC12:PIC12CE519-IJW -MCU_Microchip_PIC12:PIC12CE519-IP -MCU_Microchip_PIC12:PIC12CE519-ISM -MCU_Microchip_PIC12:PIC12CE519-ISN -MCU_Microchip_PIC12:PIC12CE673-IJW -MCU_Microchip_PIC12:PIC12CE673-IP -MCU_Microchip_PIC12:PIC12CE674-IJW -MCU_Microchip_PIC12:PIC12CE674-IP -MCU_Microchip_PIC12:PIC12CR509A-IP -MCU_Microchip_PIC12:PIC12CR509A-ISM -MCU_Microchip_PIC12:PIC12CR509A-ISN -MCU_Microchip_PIC12:PIC12F1501-IMC -MCU_Microchip_PIC12:PIC12F1501-IMS -MCU_Microchip_PIC12:PIC12F1501-IP -MCU_Microchip_PIC12:PIC12F1501-ISN -MCU_Microchip_PIC12:PIC12F1822-IMC -MCU_Microchip_PIC12:PIC12F1822-IP -MCU_Microchip_PIC12:PIC12F1822-ISN -MCU_Microchip_PIC12:PIC12F1840-IMC -MCU_Microchip_PIC12:PIC12F1840-IP -MCU_Microchip_PIC12:PIC12F1840-ISN -MCU_Microchip_PIC12:PIC12F508-IMC -MCU_Microchip_PIC12:PIC12F508-IMS -MCU_Microchip_PIC12:PIC12F508-IP -MCU_Microchip_PIC12:PIC12F508-ISN -MCU_Microchip_PIC12:PIC12F509-IMC -MCU_Microchip_PIC12:PIC12F509-IMS -MCU_Microchip_PIC12:PIC12F509-IP -MCU_Microchip_PIC12:PIC12F509-ISN -MCU_Microchip_PIC12:PIC12F510-IMC -MCU_Microchip_PIC12:PIC12F510-IMS -MCU_Microchip_PIC12:PIC12F510-IP -MCU_Microchip_PIC12:PIC12F510-ISN -MCU_Microchip_PIC12:PIC12F519-IMC -MCU_Microchip_PIC12:PIC12F519-IMS -MCU_Microchip_PIC12:PIC12F519-IP -MCU_Microchip_PIC12:PIC12F519-ISN -MCU_Microchip_PIC12:PIC12F609-IMC -MCU_Microchip_PIC12:PIC12F609-IMS -MCU_Microchip_PIC12:PIC12F609-IP -MCU_Microchip_PIC12:PIC12F609-ISN -MCU_Microchip_PIC12:PIC12F615-IMC -MCU_Microchip_PIC12:PIC12F615-IMS -MCU_Microchip_PIC12:PIC12F615-IP -MCU_Microchip_PIC12:PIC12F615-ISN -MCU_Microchip_PIC12:PIC12F617-IMC -MCU_Microchip_PIC12:PIC12F617-IMS -MCU_Microchip_PIC12:PIC12F617-IP -MCU_Microchip_PIC12:PIC12F617-ISN -MCU_Microchip_PIC12:PIC12F629-IMC -MCU_Microchip_PIC12:PIC12F629-IMS -MCU_Microchip_PIC12:PIC12F629-IP -MCU_Microchip_PIC12:PIC12F629-ISN -MCU_Microchip_PIC12:PIC12F635-IMC -MCU_Microchip_PIC12:PIC12F635-IMS -MCU_Microchip_PIC12:PIC12F635-IP -MCU_Microchip_PIC12:PIC12F635-ISN -MCU_Microchip_PIC12:PIC12F675-IMC -MCU_Microchip_PIC12:PIC12F675-IMS -MCU_Microchip_PIC12:PIC12F675-IP -MCU_Microchip_PIC12:PIC12F675-ISN -MCU_Microchip_PIC12:PIC12F683-IMC -MCU_Microchip_PIC12:PIC12F683-IMS -MCU_Microchip_PIC12:PIC12F683-IP -MCU_Microchip_PIC12:PIC12F683-ISN -MCU_Microchip_PIC12:PIC12F752-IMC -MCU_Microchip_PIC12:PIC12F752-IP -MCU_Microchip_PIC12:PIC12F752-ISN -MCU_Microchip_PIC12:PIC12HV609-IMC -MCU_Microchip_PIC12:PIC12HV609-IMS -MCU_Microchip_PIC12:PIC12HV609-IP -MCU_Microchip_PIC12:PIC12HV609-ISN -MCU_Microchip_PIC12:PIC12HV615-IMC -MCU_Microchip_PIC12:PIC12HV615-IMS -MCU_Microchip_PIC12:PIC12HV615-IP -MCU_Microchip_PIC12:PIC12HV615-ISN -MCU_Microchip_PIC12:PIC12HV752-IMC -MCU_Microchip_PIC12:PIC12HV752-IP -MCU_Microchip_PIC12:PIC12HV752-ISN -MCU_Microchip_PIC12:PIC12LF1501-IMC -MCU_Microchip_PIC12:PIC12LF1501-IMS -MCU_Microchip_PIC12:PIC12LF1501-IP -MCU_Microchip_PIC12:PIC12LF1501-ISN -MCU_Microchip_PIC12:PIC12LF1822-IMC -MCU_Microchip_PIC12:PIC12LF1822-IP -MCU_Microchip_PIC12:PIC12LF1822-ISN -MCU_Microchip_PIC12:PIC12LF1840-IMC -MCU_Microchip_PIC12:PIC12LF1840-IP -MCU_Microchip_PIC12:PIC12LF1840-ISN -MCU_Microchip_PIC12:PIC12LF1840T48-IST +MCU_Microchip_PIC12:PIC12C508-xJW +MCU_Microchip_PIC12:PIC12C508-xP +MCU_Microchip_PIC12:PIC12C508-xSM +MCU_Microchip_PIC12:PIC12C508A-xJW +MCU_Microchip_PIC12:PIC12C508A-xP +MCU_Microchip_PIC12:PIC12C508A-xSM +MCU_Microchip_PIC12:PIC12C508A-xSN +MCU_Microchip_PIC12:PIC12C509-xJW +MCU_Microchip_PIC12:PIC12C509-xP +MCU_Microchip_PIC12:PIC12C509-xSM +MCU_Microchip_PIC12:PIC12C509A-xJW +MCU_Microchip_PIC12:PIC12C509A-xP +MCU_Microchip_PIC12:PIC12C509A-xSM +MCU_Microchip_PIC12:PIC12C509A-xSN +MCU_Microchip_PIC12:PIC12C671-xJW +MCU_Microchip_PIC12:PIC12C671-xP +MCU_Microchip_PIC12:PIC12C671-xSN +MCU_Microchip_PIC12:PIC12C672-xJW +MCU_Microchip_PIC12:PIC12C672-xP +MCU_Microchip_PIC12:PIC12C672-xSN +MCU_Microchip_PIC12:PIC12CE518-xJW +MCU_Microchip_PIC12:PIC12CE518-xP +MCU_Microchip_PIC12:PIC12CE518-xSM +MCU_Microchip_PIC12:PIC12CE518-xSN +MCU_Microchip_PIC12:PIC12CE519-xJW +MCU_Microchip_PIC12:PIC12CE519-xP +MCU_Microchip_PIC12:PIC12CE519-xSM +MCU_Microchip_PIC12:PIC12CE519-xSN +MCU_Microchip_PIC12:PIC12CE673-xJW +MCU_Microchip_PIC12:PIC12CE673-xP +MCU_Microchip_PIC12:PIC12CE674-xJW +MCU_Microchip_PIC12:PIC12CE674-xP +MCU_Microchip_PIC12:PIC12CR509A-xP +MCU_Microchip_PIC12:PIC12CR509A-xSM +MCU_Microchip_PIC12:PIC12CR509A-xSN +MCU_Microchip_PIC12:PIC12F1501-xMC +MCU_Microchip_PIC12:PIC12F1501-xMS +MCU_Microchip_PIC12:PIC12F1501-xP +MCU_Microchip_PIC12:PIC12F1501-xSN +MCU_Microchip_PIC12:PIC12F1822-xMC +MCU_Microchip_PIC12:PIC12F1822-xP +MCU_Microchip_PIC12:PIC12F1822-xSN +MCU_Microchip_PIC12:PIC12F1840-xMC +MCU_Microchip_PIC12:PIC12F1840-xP +MCU_Microchip_PIC12:PIC12F1840-xSN +MCU_Microchip_PIC12:PIC12F508-xMC +MCU_Microchip_PIC12:PIC12F508-xMS +MCU_Microchip_PIC12:PIC12F508-xP +MCU_Microchip_PIC12:PIC12F508-xSN +MCU_Microchip_PIC12:PIC12F509-xMC +MCU_Microchip_PIC12:PIC12F509-xMS +MCU_Microchip_PIC12:PIC12F509-xP +MCU_Microchip_PIC12:PIC12F509-xSN +MCU_Microchip_PIC12:PIC12F510-xMC +MCU_Microchip_PIC12:PIC12F510-xMS +MCU_Microchip_PIC12:PIC12F510-xP +MCU_Microchip_PIC12:PIC12F510-xSN +MCU_Microchip_PIC12:PIC12F519-xMC +MCU_Microchip_PIC12:PIC12F519-xMS +MCU_Microchip_PIC12:PIC12F519-xP +MCU_Microchip_PIC12:PIC12F519-xSN +MCU_Microchip_PIC12:PIC12F609-xMC +MCU_Microchip_PIC12:PIC12F609-xMS +MCU_Microchip_PIC12:PIC12F609-xP +MCU_Microchip_PIC12:PIC12F609-xSN +MCU_Microchip_PIC12:PIC12F615-xMC +MCU_Microchip_PIC12:PIC12F615-xMS +MCU_Microchip_PIC12:PIC12F615-xP +MCU_Microchip_PIC12:PIC12F615-xSN +MCU_Microchip_PIC12:PIC12F617-xMC +MCU_Microchip_PIC12:PIC12F617-xMS +MCU_Microchip_PIC12:PIC12F617-xP +MCU_Microchip_PIC12:PIC12F617-xSN +MCU_Microchip_PIC12:PIC12F629-xMC +MCU_Microchip_PIC12:PIC12F629-xMS +MCU_Microchip_PIC12:PIC12F629-xP +MCU_Microchip_PIC12:PIC12F629-xSN +MCU_Microchip_PIC12:PIC12F635-xMC +MCU_Microchip_PIC12:PIC12F635-xMS +MCU_Microchip_PIC12:PIC12F635-xP +MCU_Microchip_PIC12:PIC12F635-xSN +MCU_Microchip_PIC12:PIC12F675-xMC +MCU_Microchip_PIC12:PIC12F675-xMS +MCU_Microchip_PIC12:PIC12F675-xP +MCU_Microchip_PIC12:PIC12F675-xSN +MCU_Microchip_PIC12:PIC12F683-xMC +MCU_Microchip_PIC12:PIC12F683-xMS +MCU_Microchip_PIC12:PIC12F683-xP +MCU_Microchip_PIC12:PIC12F683-xSN +MCU_Microchip_PIC12:PIC12F752-xMC +MCU_Microchip_PIC12:PIC12F752-xP +MCU_Microchip_PIC12:PIC12F752-xSN +MCU_Microchip_PIC12:PIC12HV609-xMC +MCU_Microchip_PIC12:PIC12HV609-xMS +MCU_Microchip_PIC12:PIC12HV609-xP +MCU_Microchip_PIC12:PIC12HV609-xSN +MCU_Microchip_PIC12:PIC12HV615-xMC +MCU_Microchip_PIC12:PIC12HV615-xMS +MCU_Microchip_PIC12:PIC12HV615-xP +MCU_Microchip_PIC12:PIC12HV615-xSN +MCU_Microchip_PIC12:PIC12HV752-xMC +MCU_Microchip_PIC12:PIC12HV752-xP +MCU_Microchip_PIC12:PIC12HV752-xSN +MCU_Microchip_PIC12:PIC12LF1501-xMC +MCU_Microchip_PIC12:PIC12LF1501-xMS +MCU_Microchip_PIC12:PIC12LF1501-xP +MCU_Microchip_PIC12:PIC12LF1501-xSN +MCU_Microchip_PIC12:PIC12LF1822-xMC +MCU_Microchip_PIC12:PIC12LF1822-xP +MCU_Microchip_PIC12:PIC12LF1822-xSN +MCU_Microchip_PIC12:PIC12LF1840-xMC +MCU_Microchip_PIC12:PIC12LF1840-xP +MCU_Microchip_PIC12:PIC12LF1840-xSN +MCU_Microchip_PIC12:PIC12LF1840T48-xST MCU_Microchip_PIC16:PIC16C505-IP MCU_Microchip_PIC16:PIC16C505-ISL MCU_Microchip_PIC16:PIC16C505-IST @@ -8790,6 +9813,7 @@ MCU_Microchip_PIC16:PIC16F1526-IMR MCU_Microchip_PIC16:PIC16F1526-IPT MCU_Microchip_PIC16:PIC16F1527-IMR MCU_Microchip_PIC16:PIC16F1527-IPT +MCU_Microchip_PIC16:PIC16F15323-xSL MCU_Microchip_PIC16:PIC16F15356-xML MCU_Microchip_PIC16:PIC16F15356-xMV MCU_Microchip_PIC16:PIC16F15356-xSO @@ -9325,7 +10349,9 @@ MCU_Module:Adafruit_Feather_M0_Wifi MCU_Module:Adafruit_Feather_WICED_Wifi MCU_Module:Adafruit_HUZZAH_ESP8266_breakout MCU_Module:Arduino_Leonardo +MCU_Module:Arduino_Nano_ESP32 MCU_Module:Arduino_Nano_Every +MCU_Module:Arduino_Nano_RP2040_Connect MCU_Module:Arduino_Nano_v2.x MCU_Module:Arduino_Nano_v3.x MCU_Module:Arduino_UNO_R2 @@ -9334,6 +10360,7 @@ MCU_Module:CHIP MCU_Module:CHIP-PRO MCU_Module:Carambola2 MCU_Module:Electrosmith_Daisy_Seed_Rev4 +MCU_Module:Google_Coral MCU_Module:Maple_Mini MCU_Module:NUCLEO144-F207ZG MCU_Module:NUCLEO144-F412ZG @@ -9349,6 +10376,7 @@ MCU_Module:NUCLEO144-H743ZI MCU_Module:NUCLEO64-F411RE MCU_Module:OPOS6UL MCU_Module:OPOS6UL_NANO +MCU_Module:Olimex_MOD-WIFI-ESP8266-DEV MCU_Module:Omega2+ MCU_Module:Omega2S MCU_Module:Omega2S+ @@ -9358,19 +10386,29 @@ MCU_Module:RaspberryPi-CM3 MCU_Module:RaspberryPi-CM3+ MCU_Module:RaspberryPi-CM3+L MCU_Module:RaspberryPi-CM3-L +MCU_Module:RaspberryPi_Pico +MCU_Module:RaspberryPi_Pico_Debug +MCU_Module:RaspberryPi_Pico_Extensive +MCU_Module:RaspberryPi_Pico_W +MCU_Module:RaspberryPi_Pico_W_Debug +MCU_Module:RaspberryPi_Pico_W_Extensive MCU_Module:Sipeed-M1 +MCU_Module:Sipeed-M1W MCU_Module:VisionSOM-6UL MCU_Module:VisionSOM-6ULL MCU_Module:VisionSOM-RT MCU_Module:VisionSOM-STM32MP1 -MCU_Module:WeMos_D1_mini MCU_Nordic:nRF51x22-QFxx MCU_Nordic:nRF52810-QCxx MCU_Nordic:nRF52810-QFxx MCU_Nordic:nRF52811-QCxx MCU_Nordic:nRF52820-QDxx MCU_Nordic:nRF52832-QFxx +MCU_Nordic:nRF52833_QDxx +MCU_Nordic:nRF52833_QIxx MCU_Nordic:nRF52840 +MCU_Nordic:nRF5340-QKxx +MCU_Nordic:nRF9160-SIxA MCU_NXP_ColdFire:MCF5211CAE66 MCU_NXP_ColdFire:MCF5212CAE66 MCU_NXP_ColdFire:MCF5213-LQFP100 @@ -9846,11 +10884,14 @@ MCU_NXP_S08:MC9S08SV8CLC MCU_Parallax:P8X32A-D40 MCU_Parallax:P8X32A-M44 MCU_Parallax:P8X32A-Q44 +MCU_Puya:PY32F002AF15P MCU_RaspberryPi:RP2040 MCU_Renesas_Synergy_S1:R7FS12878xA01CFL MCU_SiFive:FE310-G000 MCU_SiFive:FE310-G002 MCU_SiFive:FU540-C000 +MCU_SiliconLabs:C8051F320-GQ +MCU_SiliconLabs:C8051F321-GM MCU_SiliconLabs:C8051F380-GQ MCU_SiliconLabs:C8051F381-GM MCU_SiliconLabs:C8051F381-GQ @@ -9870,6 +10911,14 @@ MCU_SiliconLabs:EFM32HG108F32G-C-QFN24 MCU_SiliconLabs:EFM32HG108F64G-C-QFN24 MCU_SiliconLabs:EFM32HG308F32G-C-QFN24 MCU_SiliconLabs:EFM32HG308F64G-C-QFN24 +MCU_SiliconLabs:EFM32ZG108F16-B-QFN24 +MCU_SiliconLabs:EFM32ZG108F32-B-QFN24 +MCU_SiliconLabs:EFM32ZG108F4-B-QFN24 +MCU_SiliconLabs:EFM32ZG108F8-B-QFN24 +MCU_SiliconLabs:EFM32ZG110F16-B-QFN24 +MCU_SiliconLabs:EFM32ZG110F32-B-QFN24 +MCU_SiliconLabs:EFM32ZG110F4-B-QFN24 +MCU_SiliconLabs:EFM32ZG110F8-B-QFN24 MCU_SiliconLabs:EFM8BB10F2A-A-QFN20 MCU_SiliconLabs:EFM8BB10F2G-A-QFN20 MCU_SiliconLabs:EFM8BB10F2I-A-QFN20 @@ -9888,12 +10937,17 @@ MCU_SiliconLabs:EFM8LB12F64E-C-QFP32 MCU_SiliconLabs:EFM8UB30F40G-A-QFN20 MCU_SiliconLabs:EFM8UB31F40G-A-QFN24 MCU_SiliconLabs:EFM8UB31F40G-A-QSOP24 +MCU_SiliconLabs:EFR32xG23xxxxF512xM48 MCU_STC:IAP15W205S-35x-SOP16 MCU_STC:IRC15W207S-35x-SOP16 MCU_STC:STC15W201S-35x-SOP16 MCU_STC:STC15W202S-35x-SOP16 MCU_STC:STC15W203S-35x-SOP16 MCU_STC:STC15W204S-35x-SOP16 +MCU_STC:STC8G1K04-38I-TSSOP20 +MCU_STC:STC8G1K08-38I-TSSOP20 +MCU_STC:STC8G1K08A-36I-DFN8 +MCU_STC:STC8G1K17-38I-TSSOP20 MCU_ST_STM32C0:STM32C011D6Yx MCU_ST_STM32C0:STM32C011F4Px MCU_ST_STM32C0:STM32C011F4Ux @@ -9922,6 +10976,35 @@ MCU_ST_STM32C0:STM32C031K6Tx MCU_ST_STM32C0:STM32C031K6Ux MCU_ST_STM32C0:STM32C031K_4-6_Tx MCU_ST_STM32C0:STM32C031K_4-6_Ux +MCU_ST_STM32C0:STM32C071C8Tx +MCU_ST_STM32C0:STM32C071C8TxN +MCU_ST_STM32C0:STM32C071C8Ux +MCU_ST_STM32C0:STM32C071C8UxN +MCU_ST_STM32C0:STM32C071CBTx +MCU_ST_STM32C0:STM32C071CBTxN +MCU_ST_STM32C0:STM32C071CBUx +MCU_ST_STM32C0:STM32C071CBUxN +MCU_ST_STM32C0:STM32C071F8Px +MCU_ST_STM32C0:STM32C071F8PxN +MCU_ST_STM32C0:STM32C071FBPx +MCU_ST_STM32C0:STM32C071FBPxN +MCU_ST_STM32C0:STM32C071G8Ux +MCU_ST_STM32C0:STM32C071G8UxN +MCU_ST_STM32C0:STM32C071GBUx +MCU_ST_STM32C0:STM32C071GBUxN +MCU_ST_STM32C0:STM32C071K8Tx +MCU_ST_STM32C0:STM32C071K8TxN +MCU_ST_STM32C0:STM32C071K8Ux +MCU_ST_STM32C0:STM32C071K8UxN +MCU_ST_STM32C0:STM32C071KBTx +MCU_ST_STM32C0:STM32C071KBTxN +MCU_ST_STM32C0:STM32C071KBUx +MCU_ST_STM32C0:STM32C071KBUxN +MCU_ST_STM32C0:STM32C071R8Tx +MCU_ST_STM32C0:STM32C071R8TxN +MCU_ST_STM32C0:STM32C071RBIxN +MCU_ST_STM32C0:STM32C071RBTx +MCU_ST_STM32C0:STM32C071RBTxN MCU_ST_STM32F0:STM32F030C6Tx MCU_ST_STM32F0:STM32F030C8Tx MCU_ST_STM32F0:STM32F030CCTx @@ -10854,6 +11937,7 @@ MCU_ST_STM32F7:STM32F767VIHx MCU_ST_STM32F7:STM32F767VITx MCU_ST_STM32F7:STM32F767ZGTx MCU_ST_STM32F7:STM32F767ZITx +MCU_ST_STM32F7:STM32F768AIYx MCU_ST_STM32F7:STM32F769AGYx MCU_ST_STM32F7:STM32F769AIYx MCU_ST_STM32F7:STM32F769A_G-I_Yx @@ -10978,41 +12062,17 @@ MCU_ST_STM32G0:STM32G061K_6-8_Ux MCU_ST_STM32G0:STM32G070CBTx MCU_ST_STM32G0:STM32G070KBTx MCU_ST_STM32G0:STM32G070RBTx -MCU_ST_STM32G0:STM32G071C6Tx -MCU_ST_STM32G0:STM32G071C6Ux -MCU_ST_STM32G0:STM32G071C8Tx -MCU_ST_STM32G0:STM32G071C8Ux -MCU_ST_STM32G0:STM32G071CBTx -MCU_ST_STM32G0:STM32G071CBUx -MCU_ST_STM32G0:STM32G071C_6-8-B_Tx -MCU_ST_STM32G0:STM32G071C_6-8-B_Ux MCU_ST_STM32G0:STM32G071EBYx -MCU_ST_STM32G0:STM32G071G6Ux -MCU_ST_STM32G0:STM32G071G8Ux MCU_ST_STM32G0:STM32G071G8UxN -MCU_ST_STM32G0:STM32G071GBUx MCU_ST_STM32G0:STM32G071GBUxN -MCU_ST_STM32G0:STM32G071G_6-8-B_Ux MCU_ST_STM32G0:STM32G071G_8-B_UxN -MCU_ST_STM32G0:STM32G071K6Tx -MCU_ST_STM32G0:STM32G071K6Ux -MCU_ST_STM32G0:STM32G071K8Tx MCU_ST_STM32G0:STM32G071K8TxN -MCU_ST_STM32G0:STM32G071K8Ux MCU_ST_STM32G0:STM32G071K8UxN -MCU_ST_STM32G0:STM32G071KBTx MCU_ST_STM32G0:STM32G071KBTxN -MCU_ST_STM32G0:STM32G071KBUx MCU_ST_STM32G0:STM32G071KBUxN -MCU_ST_STM32G0:STM32G071K_6-8-B_Tx -MCU_ST_STM32G0:STM32G071K_6-8-B_Ux MCU_ST_STM32G0:STM32G071K_8-B_TxN MCU_ST_STM32G0:STM32G071K_8-B_UxN -MCU_ST_STM32G0:STM32G071R6Tx -MCU_ST_STM32G0:STM32G071R8Tx MCU_ST_STM32G0:STM32G071RBIx -MCU_ST_STM32G0:STM32G071RBTx -MCU_ST_STM32G0:STM32G071R_6-8-B_Tx MCU_ST_STM32G0:STM32G081CBTx MCU_ST_STM32G0:STM32G081CBUx MCU_ST_STM32G0:STM32G081EBYx @@ -11133,6 +12193,7 @@ MCU_ST_STM32G4:STM32G431C6Ux MCU_ST_STM32G4:STM32G431C8Tx MCU_ST_STM32G4:STM32G431C8Ux MCU_ST_STM32G4:STM32G431CBTx +MCU_ST_STM32G4:STM32G431CBTxZ MCU_ST_STM32G4:STM32G431CBUx MCU_ST_STM32G4:STM32G431CBYx MCU_ST_STM32G4:STM32G431C_6-8-B_Tx @@ -11155,6 +12216,7 @@ MCU_ST_STM32G4:STM32G431R8Ix MCU_ST_STM32G4:STM32G431R8Tx MCU_ST_STM32G4:STM32G431RBIx MCU_ST_STM32G4:STM32G431RBTx +MCU_ST_STM32G4:STM32G431RBTxZ MCU_ST_STM32G4:STM32G431R_6-8-B_Ix MCU_ST_STM32G4:STM32G431R_6-8-B_Tx MCU_ST_STM32G4:STM32G431V6Tx @@ -11190,10 +12252,12 @@ MCU_ST_STM32G4:STM32G473P_B-C-E_Ix MCU_ST_STM32G4:STM32G473QBTx MCU_ST_STM32G4:STM32G473QCTx MCU_ST_STM32G4:STM32G473QETx +MCU_ST_STM32G4:STM32G473QETxZ MCU_ST_STM32G4:STM32G473Q_B-C-E_Tx MCU_ST_STM32G4:STM32G473RBTx MCU_ST_STM32G4:STM32G473RCTx MCU_ST_STM32G4:STM32G473RETx +MCU_ST_STM32G4:STM32G473RETxZ MCU_ST_STM32G4:STM32G473R_B-C-E_Tx MCU_ST_STM32G4:STM32G473VBHx MCU_ST_STM32G4:STM32G473VBTx @@ -11273,6 +12337,7 @@ MCU_ST_STM32G4:STM32G491RCIx MCU_ST_STM32G4:STM32G491RCTx MCU_ST_STM32G4:STM32G491REIx MCU_ST_STM32G4:STM32G491RETx +MCU_ST_STM32G4:STM32G491RETxZ MCU_ST_STM32G4:STM32G491REYx MCU_ST_STM32G4:STM32G491R_C-E_Ix MCU_ST_STM32G4:STM32G491R_C-E_Tx @@ -11288,6 +12353,79 @@ MCU_ST_STM32G4:STM32G4A1REIx MCU_ST_STM32G4:STM32G4A1RETx MCU_ST_STM32G4:STM32G4A1REYx MCU_ST_STM32G4:STM32G4A1VETx +MCU_ST_STM32H5:STM32H503CBTx +MCU_ST_STM32H5:STM32H503CBUx +MCU_ST_STM32H5:STM32H503EBYx +MCU_ST_STM32H5:STM32H503KBUx +MCU_ST_STM32H5:STM32H503RBTx +MCU_ST_STM32H5:STM32H523CCTx +MCU_ST_STM32H5:STM32H523CCUx +MCU_ST_STM32H5:STM32H523CETx +MCU_ST_STM32H5:STM32H523CEUx +MCU_ST_STM32H5:STM32H523RCTx +MCU_ST_STM32H5:STM32H523RETx +MCU_ST_STM32H5:STM32H523VCIx +MCU_ST_STM32H5:STM32H523VCTx +MCU_ST_STM32H5:STM32H523VEIx +MCU_ST_STM32H5:STM32H523VETx +MCU_ST_STM32H5:STM32H523ZCJx +MCU_ST_STM32H5:STM32H523ZCTx +MCU_ST_STM32H5:STM32H523ZEJx +MCU_ST_STM32H5:STM32H523ZETx +MCU_ST_STM32H5:STM32H533CETx +MCU_ST_STM32H5:STM32H533CEUx +MCU_ST_STM32H5:STM32H533RETx +MCU_ST_STM32H5:STM32H533VEIx +MCU_ST_STM32H5:STM32H533VETx +MCU_ST_STM32H5:STM32H533ZEJx +MCU_ST_STM32H5:STM32H533ZETx +MCU_ST_STM32H5:STM32H562AGIx +MCU_ST_STM32H5:STM32H562AIIx +MCU_ST_STM32H5:STM32H562IGKx +MCU_ST_STM32H5:STM32H562IGTx +MCU_ST_STM32H5:STM32H562IIKx +MCU_ST_STM32H5:STM32H562IITx +MCU_ST_STM32H5:STM32H562RGTx +MCU_ST_STM32H5:STM32H562RGVx +MCU_ST_STM32H5:STM32H562RITx +MCU_ST_STM32H5:STM32H562RIVx +MCU_ST_STM32H5:STM32H562VGTx +MCU_ST_STM32H5:STM32H562VITx +MCU_ST_STM32H5:STM32H562ZGTx +MCU_ST_STM32H5:STM32H562ZITx +MCU_ST_STM32H5:STM32H563AGIx +MCU_ST_STM32H5:STM32H563AIIx +MCU_ST_STM32H5:STM32H563AIIxQ +MCU_ST_STM32H5:STM32H563IGKx +MCU_ST_STM32H5:STM32H563IGTx +MCU_ST_STM32H5:STM32H563IIKx +MCU_ST_STM32H5:STM32H563IIKxQ +MCU_ST_STM32H5:STM32H563IITx +MCU_ST_STM32H5:STM32H563IITxQ +MCU_ST_STM32H5:STM32H563MIYxQ +MCU_ST_STM32H5:STM32H563RGTx +MCU_ST_STM32H5:STM32H563RGVx +MCU_ST_STM32H5:STM32H563RITx +MCU_ST_STM32H5:STM32H563RIVx +MCU_ST_STM32H5:STM32H563VGTx +MCU_ST_STM32H5:STM32H563VITx +MCU_ST_STM32H5:STM32H563VITxQ +MCU_ST_STM32H5:STM32H563ZGTx +MCU_ST_STM32H5:STM32H563ZITx +MCU_ST_STM32H5:STM32H563ZITxQ +MCU_ST_STM32H5:STM32H573AIIx +MCU_ST_STM32H5:STM32H573AIIxQ +MCU_ST_STM32H5:STM32H573IIKx +MCU_ST_STM32H5:STM32H573IIKxQ +MCU_ST_STM32H5:STM32H573IITx +MCU_ST_STM32H5:STM32H573IITxQ +MCU_ST_STM32H5:STM32H573MIYxQ +MCU_ST_STM32H5:STM32H573RITx +MCU_ST_STM32H5:STM32H573RIVx +MCU_ST_STM32H5:STM32H573VITx +MCU_ST_STM32H5:STM32H573VITxQ +MCU_ST_STM32H5:STM32H573ZITx +MCU_ST_STM32H5:STM32H573ZITxQ MCU_ST_STM32H7:STM32H723VEHx MCU_ST_STM32H7:STM32H723VETx MCU_ST_STM32H7:STM32H723VGHx @@ -11479,6 +12617,40 @@ MCU_ST_STM32H7:STM32H7B3VITx MCU_ST_STM32H7:STM32H7B3VITxQ MCU_ST_STM32H7:STM32H7B3ZITx MCU_ST_STM32H7:STM32H7B3ZITxQ +MCU_ST_STM32H7:STM32H7R3A8Ix +MCU_ST_STM32H7:STM32H7R3I8Kx +MCU_ST_STM32H7:STM32H7R3I8Tx +MCU_ST_STM32H7:STM32H7R3L8Hx +MCU_ST_STM32H7:STM32H7R3L8HxH +MCU_ST_STM32H7:STM32H7R3R8Vx +MCU_ST_STM32H7:STM32H7R3V8Hx +MCU_ST_STM32H7:STM32H7R3V8Tx +MCU_ST_STM32H7:STM32H7R3V8Yx +MCU_ST_STM32H7:STM32H7R3Z8Jx +MCU_ST_STM32H7:STM32H7R3Z8Tx +MCU_ST_STM32H7:STM32H7R7A8Ix +MCU_ST_STM32H7:STM32H7R7I8Kx +MCU_ST_STM32H7:STM32H7R7I8Tx +MCU_ST_STM32H7:STM32H7R7L8Hx +MCU_ST_STM32H7:STM32H7R7L8HxH +MCU_ST_STM32H7:STM32H7R7Z8Jx +MCU_ST_STM32H7:STM32H7S3A8Ix +MCU_ST_STM32H7:STM32H7S3I8Kx +MCU_ST_STM32H7:STM32H7S3I8Tx +MCU_ST_STM32H7:STM32H7S3L8Hx +MCU_ST_STM32H7:STM32H7S3L8HxH +MCU_ST_STM32H7:STM32H7S3R8Vx +MCU_ST_STM32H7:STM32H7S3V8Hx +MCU_ST_STM32H7:STM32H7S3V8Tx +MCU_ST_STM32H7:STM32H7S3V8Yx +MCU_ST_STM32H7:STM32H7S3Z8Jx +MCU_ST_STM32H7:STM32H7S3Z8Tx +MCU_ST_STM32H7:STM32H7S7A8Ix +MCU_ST_STM32H7:STM32H7S7I8Kx +MCU_ST_STM32H7:STM32H7S7I8Tx +MCU_ST_STM32H7:STM32H7S7L8Hx +MCU_ST_STM32H7:STM32H7S7L8HxH +MCU_ST_STM32H7:STM32H7S7Z8Jx MCU_ST_STM32L0:STM32L010C6Tx MCU_ST_STM32L0:STM32L010F4Px MCU_ST_STM32L0:STM32L010K4Tx @@ -12054,6 +13226,7 @@ MCU_ST_STM32L4:STM32L476R_C-E-G_Tx MCU_ST_STM32L4:STM32L476VCTx MCU_ST_STM32L4:STM32L476VETx MCU_ST_STM32L4:STM32L476VGTx +MCU_ST_STM32L4:STM32L476VGYxP MCU_ST_STM32L4:STM32L476V_C-E-G_Tx MCU_ST_STM32L4:STM32L476ZETx MCU_ST_STM32L4:STM32L476ZGJx @@ -12245,6 +13418,42 @@ MCU_ST_STM32L5:STM32L562VETx MCU_ST_STM32L5:STM32L562VETxQ MCU_ST_STM32L5:STM32L562ZETx MCU_ST_STM32L5:STM32L562ZETxQ +MCU_ST_STM32MP1:STM32MP131AAEx +MCU_ST_STM32MP1:STM32MP131AAFx +MCU_ST_STM32MP1:STM32MP131AAGx +MCU_ST_STM32MP1:STM32MP131CAEx +MCU_ST_STM32MP1:STM32MP131CAFx +MCU_ST_STM32MP1:STM32MP131CAGx +MCU_ST_STM32MP1:STM32MP131DAEx +MCU_ST_STM32MP1:STM32MP131DAFx +MCU_ST_STM32MP1:STM32MP131DAGx +MCU_ST_STM32MP1:STM32MP131FAEx +MCU_ST_STM32MP1:STM32MP131FAFx +MCU_ST_STM32MP1:STM32MP131FAGx +MCU_ST_STM32MP1:STM32MP133AAEx +MCU_ST_STM32MP1:STM32MP133AAFx +MCU_ST_STM32MP1:STM32MP133AAGx +MCU_ST_STM32MP1:STM32MP133CAEx +MCU_ST_STM32MP1:STM32MP133CAFx +MCU_ST_STM32MP1:STM32MP133CAGx +MCU_ST_STM32MP1:STM32MP133DAEx +MCU_ST_STM32MP1:STM32MP133DAFx +MCU_ST_STM32MP1:STM32MP133DAGx +MCU_ST_STM32MP1:STM32MP133FAEx +MCU_ST_STM32MP1:STM32MP133FAFx +MCU_ST_STM32MP1:STM32MP133FAGx +MCU_ST_STM32MP1:STM32MP135AAEx +MCU_ST_STM32MP1:STM32MP135AAFx +MCU_ST_STM32MP1:STM32MP135AAGx +MCU_ST_STM32MP1:STM32MP135CAEx +MCU_ST_STM32MP1:STM32MP135CAFx +MCU_ST_STM32MP1:STM32MP135CAGx +MCU_ST_STM32MP1:STM32MP135DAEx +MCU_ST_STM32MP1:STM32MP135DAFx +MCU_ST_STM32MP1:STM32MP135DAGx +MCU_ST_STM32MP1:STM32MP135FAEx +MCU_ST_STM32MP1:STM32MP135FAFx +MCU_ST_STM32MP1:STM32MP135FAGx MCU_ST_STM32MP1:STM32MP151AAAx MCU_ST_STM32MP1:STM32MP151AABx MCU_ST_STM32MP1:STM32MP151AACx @@ -12293,6 +13502,103 @@ MCU_ST_STM32MP1:STM32MP157FAAx MCU_ST_STM32MP1:STM32MP157FABx MCU_ST_STM32MP1:STM32MP157FACx MCU_ST_STM32MP1:STM32MP157FADx +MCU_ST_STM32U0:STM32U031C6Tx +MCU_ST_STM32U0:STM32U031C6Ux +MCU_ST_STM32U0:STM32U031C8Tx +MCU_ST_STM32U0:STM32U031C8Ux +MCU_ST_STM32U0:STM32U031F4Px +MCU_ST_STM32U0:STM32U031F6Px +MCU_ST_STM32U0:STM32U031F8Px +MCU_ST_STM32U0:STM32U031G6Yx +MCU_ST_STM32U0:STM32U031G8Yx +MCU_ST_STM32U0:STM32U031K4Ux +MCU_ST_STM32U0:STM32U031K6Ux +MCU_ST_STM32U0:STM32U031K8Ux +MCU_ST_STM32U0:STM32U031R6Ix +MCU_ST_STM32U0:STM32U031R6Tx +MCU_ST_STM32U0:STM32U031R8Ix +MCU_ST_STM32U0:STM32U031R8Tx +MCU_ST_STM32U0:STM32U073C8Tx +MCU_ST_STM32U0:STM32U073C8Ux +MCU_ST_STM32U0:STM32U073CBTx +MCU_ST_STM32U0:STM32U073CBUx +MCU_ST_STM32U0:STM32U073CCTx +MCU_ST_STM32U0:STM32U073CCUx +MCU_ST_STM32U0:STM32U073H8Yx +MCU_ST_STM32U0:STM32U073HBYx +MCU_ST_STM32U0:STM32U073HCYx +MCU_ST_STM32U0:STM32U073K8Ux +MCU_ST_STM32U0:STM32U073KBUx +MCU_ST_STM32U0:STM32U073KCUx +MCU_ST_STM32U0:STM32U073M8Ix +MCU_ST_STM32U0:STM32U073M8Tx +MCU_ST_STM32U0:STM32U073MBIx +MCU_ST_STM32U0:STM32U073MBTx +MCU_ST_STM32U0:STM32U073MCIx +MCU_ST_STM32U0:STM32U073MCTx +MCU_ST_STM32U0:STM32U073R8Ix +MCU_ST_STM32U0:STM32U073R8Tx +MCU_ST_STM32U0:STM32U073RBIx +MCU_ST_STM32U0:STM32U073RBTx +MCU_ST_STM32U0:STM32U073RCIx +MCU_ST_STM32U0:STM32U073RCTx +MCU_ST_STM32U0:STM32U083CCTx +MCU_ST_STM32U0:STM32U083CCUx +MCU_ST_STM32U0:STM32U083HCYx +MCU_ST_STM32U0:STM32U083KCUx +MCU_ST_STM32U0:STM32U083MCIx +MCU_ST_STM32U0:STM32U083MCTx +MCU_ST_STM32U0:STM32U083RCIx +MCU_ST_STM32U0:STM32U083RCTx +MCU_ST_STM32U5:STM32U535CBTx +MCU_ST_STM32U5:STM32U535CBTxQ +MCU_ST_STM32U5:STM32U535CBUx +MCU_ST_STM32U5:STM32U535CBUxQ +MCU_ST_STM32U5:STM32U535CCTx +MCU_ST_STM32U5:STM32U535CCTxQ +MCU_ST_STM32U5:STM32U535CCUx +MCU_ST_STM32U5:STM32U535CCUxQ +MCU_ST_STM32U5:STM32U535CETx +MCU_ST_STM32U5:STM32U535CETxQ +MCU_ST_STM32U5:STM32U535CEUx +MCU_ST_STM32U5:STM32U535CEUxQ +MCU_ST_STM32U5:STM32U535JEYxQ +MCU_ST_STM32U5:STM32U535NCYxQ +MCU_ST_STM32U5:STM32U535NEYxQ +MCU_ST_STM32U5:STM32U535RBIx +MCU_ST_STM32U5:STM32U535RBIxQ +MCU_ST_STM32U5:STM32U535RBTx +MCU_ST_STM32U5:STM32U535RBTxQ +MCU_ST_STM32U5:STM32U535RCIx +MCU_ST_STM32U5:STM32U535RCIxQ +MCU_ST_STM32U5:STM32U535RCTx +MCU_ST_STM32U5:STM32U535RCTxQ +MCU_ST_STM32U5:STM32U535REIx +MCU_ST_STM32U5:STM32U535REIxQ +MCU_ST_STM32U5:STM32U535RETx +MCU_ST_STM32U5:STM32U535RETxQ +MCU_ST_STM32U5:STM32U535VCIx +MCU_ST_STM32U5:STM32U535VCIxQ +MCU_ST_STM32U5:STM32U535VCTx +MCU_ST_STM32U5:STM32U535VCTxQ +MCU_ST_STM32U5:STM32U535VEIx +MCU_ST_STM32U5:STM32U535VEIxQ +MCU_ST_STM32U5:STM32U535VETx +MCU_ST_STM32U5:STM32U535VETxQ +MCU_ST_STM32U5:STM32U545CETx +MCU_ST_STM32U5:STM32U545CETxQ +MCU_ST_STM32U5:STM32U545CEUx +MCU_ST_STM32U5:STM32U545CEUxQ +MCU_ST_STM32U5:STM32U545JEYxQ +MCU_ST_STM32U5:STM32U545NEYxQ +MCU_ST_STM32U5:STM32U545REIx +MCU_ST_STM32U5:STM32U545REIxQ +MCU_ST_STM32U5:STM32U545RETx +MCU_ST_STM32U5:STM32U545RETxQ +MCU_ST_STM32U5:STM32U545VEIx +MCU_ST_STM32U5:STM32U545VEIxQ +MCU_ST_STM32U5:STM32U545VETx +MCU_ST_STM32U5:STM32U545VETxQ MCU_ST_STM32U5:STM32U575AGIx MCU_ST_STM32U5:STM32U575AGIxQ MCU_ST_STM32U5:STM32U575AIIx @@ -12338,6 +13644,78 @@ MCU_ST_STM32U5:STM32U585VITx MCU_ST_STM32U5:STM32U585VITxQ MCU_ST_STM32U5:STM32U585ZITx MCU_ST_STM32U5:STM32U585ZITxQ +MCU_ST_STM32U5:STM32U595AIHx +MCU_ST_STM32U5:STM32U595AIHxQ +MCU_ST_STM32U5:STM32U595AJHx +MCU_ST_STM32U5:STM32U595AJHxQ +MCU_ST_STM32U5:STM32U595QIIx +MCU_ST_STM32U5:STM32U595QIIxQ +MCU_ST_STM32U5:STM32U595QJIx +MCU_ST_STM32U5:STM32U595QJIxQ +MCU_ST_STM32U5:STM32U595RITx +MCU_ST_STM32U5:STM32U595RITxQ +MCU_ST_STM32U5:STM32U595RJTx +MCU_ST_STM32U5:STM32U595RJTxQ +MCU_ST_STM32U5:STM32U595VITx +MCU_ST_STM32U5:STM32U595VITxQ +MCU_ST_STM32U5:STM32U595VJTx +MCU_ST_STM32U5:STM32U595VJTxQ +MCU_ST_STM32U5:STM32U595ZITx +MCU_ST_STM32U5:STM32U595ZITxQ +MCU_ST_STM32U5:STM32U595ZIYxQ +MCU_ST_STM32U5:STM32U595ZJTx +MCU_ST_STM32U5:STM32U595ZJTxQ +MCU_ST_STM32U5:STM32U595ZJYxQ +MCU_ST_STM32U5:STM32U599BJYxQ +MCU_ST_STM32U5:STM32U599NIHxQ +MCU_ST_STM32U5:STM32U599NJHxQ +MCU_ST_STM32U5:STM32U599VITxQ +MCU_ST_STM32U5:STM32U599VJTx +MCU_ST_STM32U5:STM32U599VJTxQ +MCU_ST_STM32U5:STM32U599ZITxQ +MCU_ST_STM32U5:STM32U599ZIYxQ +MCU_ST_STM32U5:STM32U599ZJTxQ +MCU_ST_STM32U5:STM32U599ZJYxQ +MCU_ST_STM32U5:STM32U5A5AJHx +MCU_ST_STM32U5:STM32U5A5AJHxQ +MCU_ST_STM32U5:STM32U5A5QIIxQ +MCU_ST_STM32U5:STM32U5A5QJIx +MCU_ST_STM32U5:STM32U5A5QJIxQ +MCU_ST_STM32U5:STM32U5A5RJTx +MCU_ST_STM32U5:STM32U5A5RJTxQ +MCU_ST_STM32U5:STM32U5A5VJTx +MCU_ST_STM32U5:STM32U5A5VJTxQ +MCU_ST_STM32U5:STM32U5A5ZJTx +MCU_ST_STM32U5:STM32U5A5ZJTxQ +MCU_ST_STM32U5:STM32U5A5ZJYxQ +MCU_ST_STM32U5:STM32U5A9BJYxQ +MCU_ST_STM32U5:STM32U5A9NJHxQ +MCU_ST_STM32U5:STM32U5A9VJTxQ +MCU_ST_STM32U5:STM32U5A9ZJTxQ +MCU_ST_STM32U5:STM32U5A9ZJYxQ +MCU_ST_STM32U5:STM32U5F7VITx +MCU_ST_STM32U5:STM32U5F7VITxQ +MCU_ST_STM32U5:STM32U5F7VJTx +MCU_ST_STM32U5:STM32U5F7VJTxQ +MCU_ST_STM32U5:STM32U5F9BJYxQ +MCU_ST_STM32U5:STM32U5F9NJHxQ +MCU_ST_STM32U5:STM32U5F9VITxQ +MCU_ST_STM32U5:STM32U5F9VJTxQ +MCU_ST_STM32U5:STM32U5F9ZIJxQ +MCU_ST_STM32U5:STM32U5F9ZITxQ +MCU_ST_STM32U5:STM32U5F9ZJJxQ +MCU_ST_STM32U5:STM32U5F9ZJTxQ +MCU_ST_STM32U5:STM32U5G7VJTx +MCU_ST_STM32U5:STM32U5G7VJTxQ +MCU_ST_STM32U5:STM32U5G9BJYxQ +MCU_ST_STM32U5:STM32U5G9NJHxQ +MCU_ST_STM32U5:STM32U5G9VJTxQ +MCU_ST_STM32U5:STM32U5G9ZJJxQ +MCU_ST_STM32U5:STM32U5G9ZJTxQ +MCU_ST_STM32WB:STM32WB05KZVx +MCU_ST_STM32WB:STM32WB06KCVx +MCU_ST_STM32WB:STM32WB07KCVx +MCU_ST_STM32WB:STM32WB09KEVx MCU_ST_STM32WB:STM32WB10CCUx MCU_ST_STM32WB:STM32WB15CCUx MCU_ST_STM32WB:STM32WB15CCUxE @@ -12360,6 +13738,20 @@ MCU_ST_STM32WB:STM32WB55VEYx MCU_ST_STM32WB:STM32WB55VGQx MCU_ST_STM32WB:STM32WB55VGYx MCU_ST_STM32WB:STM32WB55VYYx +MCU_ST_STM32WB:STM32WBA52CEUx +MCU_ST_STM32WB:STM32WBA52CGUx +MCU_ST_STM32WB:STM32WBA52KEUx +MCU_ST_STM32WB:STM32WBA52KGUx +MCU_ST_STM32WB:STM32WBA54CEUx +MCU_ST_STM32WB:STM32WBA54CGUx +MCU_ST_STM32WB:STM32WBA54KEUx +MCU_ST_STM32WB:STM32WBA54KGUx +MCU_ST_STM32WB:STM32WBA55CEUx +MCU_ST_STM32WB:STM32WBA55CGUx +MCU_ST_STM32WB:STM32WBA55HEFx +MCU_ST_STM32WB:STM32WBA55HGFx +MCU_ST_STM32WB:STM32WBA55UEIx +MCU_ST_STM32WB:STM32WBA55UGIx MCU_ST_STM32WL:STM32WL54CCUx MCU_ST_STM32WL:STM32WL54JCIx MCU_ST_STM32WL:STM32WL55CCUx @@ -12401,12 +13793,14 @@ MCU_ST_STM8:STM8S003K3T MCU_ST_STM8:STM8S207C6 MCU_ST_STM8:STM8S207C8 MCU_ST_STM8:STM8S207CB +MCU_ST_STM8:STM8S207MB MCU_ST_STM8:STM8S207R6 MCU_ST_STM8:STM8S207R8 MCU_ST_STM8:STM8S207RB MCU_ST_STM8:STM8S208C6 MCU_ST_STM8:STM8S208C8 MCU_ST_STM8:STM8S208CB +MCU_ST_STM8:STM8S208MB MCU_ST_STM8:STM8S208R6 MCU_ST_STM8:STM8S208R8 MCU_ST_STM8:STM8S208RB @@ -12793,6 +14187,19 @@ MCU_Texas_MSP430:MSP430G2855IRHA40 MCU_Texas_MSP430:MSP430G2955IDA38 MCU_Texas_MSP430:MSP430G2955IRHA40 MCU_Texas_SimpleLink:CC1312R1F3RGZ +MCU_WCH_CH32V0:CH32V003AxMx +MCU_WCH_CH32V0:CH32V003FxPx +MCU_WCH_CH32V0:CH32V003FxUx +MCU_WCH_CH32V0:CH32V003JxMx +MCU_WCH_CH32V2:CH32V203CxTx +MCU_WCH_CH32V2:CH32V203F6P6 +MCU_WCH_CH32V2:CH32V203GxUx +MCU_WCH_CH32V3:CH32V30xCxTx +MCU_WCH_CH32V3:CH32V30xFxPx +MCU_WCH_CH32V3:CH32V30xRxTx +MCU_WCH_CH32V3:CH32V30xVxTx +MCU_WCH_CH32V3:CH32V30xWxUx +MCU_WCH_CH32X0:CH32X035G8U6 Mechanical:DIN_Rail_Adapter Mechanical:Fiducial Mechanical:Heatsink @@ -12805,6 +14212,10 @@ Mechanical:MountingHole Mechanical:MountingHole_Pad Mechanical:MountingHole_Pad_MP Memory_EEPROM:24AA02-OT +Memory_EEPROM:24AA025E-OT +Memory_EEPROM:24AA025E-SN +Memory_EEPROM:24AA02E-OT +Memory_EEPROM:24AA02E-SN Memory_EEPROM:24LC00 Memory_EEPROM:24LC01 Memory_EEPROM:24LC02 @@ -12821,6 +14232,7 @@ Memory_EEPROM:25CSM04xxMF Memory_EEPROM:25CSM04xxSN Memory_EEPROM:25LCxxx Memory_EEPROM:25LCxxx-MC +Memory_EEPROM:25LCxxx-MF Memory_EEPROM:28C256 Memory_EEPROM:93AAxxA Memory_EEPROM:93AAxxAT-xOT @@ -12874,6 +14286,14 @@ Memory_EEPROM:CAT24M01X Memory_EEPROM:CAT24M01Y Memory_EEPROM:CAT250xxx Memory_EEPROM:CAT250xxx-HU4 +Memory_EEPROM:DS2431 +Memory_EEPROM:DS2431P +Memory_EEPROM:DS2431Q +Memory_EEPROM:DS28E07 +Memory_EEPROM:DS28E07P +Memory_EEPROM:DS28E07Q +Memory_EEPROM:KM28C64A +Memory_EEPROM:KM28C65A Memory_EEPROM:M24C01-FDW Memory_EEPROM:M24C01-FMN Memory_EEPROM:M24C01-RDW @@ -12887,6 +14307,7 @@ Memory_EEPROM:M24C02-RMN Memory_EEPROM:M24C02-WDW Memory_EEPROM:M24C02-WMN Memory_EEPROM:M95256-WMN6P +Memory_EEPROM:M95512-Axxx-MF Memory_EEPROM:TMS4C1050N Memory_EPROM:27128 Memory_EPROM:27256 @@ -12904,6 +14325,9 @@ Memory_EPROM:27C64 Memory_Flash:28F400 Memory_Flash:29F010-TSOP-SP Memory_Flash:29W040 +Memory_Flash:AM29F400BB-90SC +Memory_Flash:AM29F400Bx-xxEx +Memory_Flash:AM29F400Bx-xxSx Memory_Flash:AM29PDL128G Memory_Flash:AT25DF041x-UxN-x Memory_Flash:AT25SF081-SSHD-X @@ -12921,6 +14345,7 @@ Memory_Flash:AT45DB161B-TC-2.5 Memory_Flash:AT45DB161D-SU Memory_Flash:GD25D05CT Memory_Flash:GD25D10CT +Memory_Flash:GD25QxxxEY Memory_Flash:IS25WP256D-xM Memory_Flash:M25PX32-VMP Memory_Flash:M25PX32-VMW @@ -12941,7 +14366,10 @@ Memory_Flash:SST25VF080B-50-4x-S2Ax Memory_Flash:SST39SF010 Memory_Flash:SST39SF020 Memory_Flash:SST39SF040 +Memory_Flash:W25Q128JVE +Memory_Flash:W25Q128JVP Memory_Flash:W25Q128JVS +Memory_Flash:W25Q16JVSS Memory_Flash:W25Q32JVSS Memory_Flash:W25Q32JVZP Memory_Flash:W25X20CLSN @@ -12957,6 +14385,13 @@ Memory_NVRAM:47C04 Memory_NVRAM:47C16 Memory_NVRAM:47L04 Memory_NVRAM:47L16 +Memory_NVRAM:CY14B256LA-SP +Memory_NVRAM:CY14B256LA-SZ +Memory_NVRAM:CY14B256LA-ZS +Memory_NVRAM:CY14E256LA-SZ +Memory_NVRAM:CY14E256LA-ZS +Memory_NVRAM:CY14U256LA-BA +Memory_NVRAM:CY14V256LA-BA Memory_NVRAM:FM1608B-SG Memory_NVRAM:FM16W08-SG Memory_NVRAM:FM1808B-SG @@ -12973,17 +14408,41 @@ Memory_NVRAM:MB85RS512T Memory_NVRAM:MB85RS64 Memory_NVRAM:MR20H40 Memory_NVRAM:MR25H40 -Memory_RAM:628128_DIP32_SSOP32 -Memory_RAM:628128_TSOP32 +Memory_NVRAM:STK14C88 +Memory_NVRAM:STK14C88-3 +Memory_NVRAM:STK14C88C +Memory_NVRAM:STK14C88C-3 Memory_RAM:AS4C256M16D3 Memory_RAM:AS4C4M16SA +Memory_RAM:AS6C1008-xxB +Memory_RAM:AS6C1008-xxP +Memory_RAM:AS6C1008-xxS +Memory_RAM:AS6C1008-xxST +Memory_RAM:AS6C1008-xxT Memory_RAM:AS6C1616 Memory_RAM:AS6C4008-55PCN +Memory_RAM:AS7C1024B-xxJ +Memory_RAM:AS7C1024B-xxT +Memory_RAM:AS7C1024B-xxTJ +Memory_RAM:AS7C31024B-xxJ +Memory_RAM:AS7C31024B-xxST +Memory_RAM:AS7C31024B-xxT +Memory_RAM:AS7C31024B-xxTJ +Memory_RAM:CY62128EV30xx-xxS +Memory_RAM:CY62128EV30xx-xxZ +Memory_RAM:CY62128Exx-xxS +Memory_RAM:CY62128Exx-xxZ Memory_RAM:CY62256-70PC Memory_RAM:CY7C199 Memory_RAM:ESP-PSRAM32 Memory_RAM:H5AN8G8NAFR-UHC Memory_RAM:HM62256BLP +Memory_RAM:HM628128D_DIP32_SOP32 +Memory_RAM:HM628128D_TSOP32 +Memory_RAM:HM628128_DIP32_SOP32 +Memory_RAM:HM628128_TSOP32 +Memory_RAM:HY6264AxJ +Memory_RAM:HY6264AxP Memory_RAM:IDT7006PF Memory_RAM:IDT7027_TQ100 Memory_RAM:IDT7132 @@ -13005,15 +14464,27 @@ Memory_RAM:IS61C5128AL-10TLI Memory_RAM:IS61C5128AS-25HLI Memory_RAM:IS61C5128AS-25QLI Memory_RAM:IS61C5128AS-25TLI +Memory_RAM:IS62C256AL Memory_RAM:IS64C5128AL-12CTLA3 Memory_RAM:IS64C5128AL-12KLA3 +Memory_RAM:IS65C256AL +Memory_RAM:IS6xC1024AL-xxH +Memory_RAM:IS6xC1024AL-xxJ +Memory_RAM:IS6xC1024AL-xxK +Memory_RAM:IS6xC1024AL-xxT Memory_RAM:KM62256CLP +Memory_RAM:M48Tx2 +Memory_RAM:M48Zx2 +Memory_RAM:MK4116N +Memory_RAM:MK4164N Memory_RAM:MT48LC16M16A2P Memory_RAM:MT48LC16M16A2TG Memory_RAM:MT48LC32M8A2P Memory_RAM:MT48LC32M8A2TG Memory_RAM:MT48LC64M4A2P Memory_RAM:MT48LC64M4A2TG +Memory_RAM:R1LP0108ESF +Memory_RAM:R1LP0108ESN Memory_RAM:W9812G6KH-5 Memory_RAM:W9812G6KH-6 Memory_RAM:W9812G6KH-6I @@ -13027,7 +14498,8 @@ Motor:Fan_3pin Motor:Fan_4pin Motor:Fan_ALT Motor:Fan_CPU_4pin -Motor:Fan_IEC60617 +Motor:Fan_IEC-60617 +Motor:Fan_ISO-14617 Motor:Fan_PC_Chassis Motor:Fan_Tacho Motor:Fan_Tacho_PWM @@ -13120,6 +14592,21 @@ Oscillator:Si5351B-B-GM Oscillator:Si5351C-B-GM Oscillator:Si570 Oscillator:Si571 +Oscillator:SiT8008xx-1x-xxE +Oscillator:SiT8008xx-1x-xxN +Oscillator:SiT8008xx-1x-xxS +Oscillator:SiT8008xx-2x-xxE +Oscillator:SiT8008xx-2x-xxN +Oscillator:SiT8008xx-2x-xxS +Oscillator:SiT8008xx-3x-xxE +Oscillator:SiT8008xx-3x-xxN +Oscillator:SiT8008xx-3x-xxS +Oscillator:SiT8008xx-7x-xxE +Oscillator:SiT8008xx-7x-xxN +Oscillator:SiT8008xx-7x-xxS +Oscillator:SiT8008xx-8x-xxE +Oscillator:SiT8008xx-8x-xxN +Oscillator:SiT8008xx-8x-xxS Oscillator:SiT9365xx-xBx-xxE Oscillator:SiT9365xx-xBx-xxN Oscillator:SiT9366xx-xBx-xxE @@ -13130,6 +14617,7 @@ Oscillator:TCXO-14 Oscillator:TCXO3 Oscillator:TFT660 Oscillator:TFT680 +Oscillator:TG2520SMN-xx.xxxxxxMhz-xxxxNM Oscillator:TXC-7C Oscillator:VC-81 Oscillator:VC-83 @@ -13181,6 +14669,14 @@ Potentiometer_Digital:MCP4024-xxxxOT Potentiometer_Digital:MCP41010 Potentiometer_Digital:MCP41050 Potentiometer_Digital:MCP41100 +Potentiometer_Digital:MCP4131-xxxx-P +Potentiometer_Digital:MCP4132-xxxx-P +Potentiometer_Digital:MCP4141-xxxx-P +Potentiometer_Digital:MCP4142-xxxx-P +Potentiometer_Digital:MCP4151-xxxx-P +Potentiometer_Digital:MCP4152-xxxx-P +Potentiometer_Digital:MCP4161-xxxx-P +Potentiometer_Digital:MCP4162-xxxx-P Potentiometer_Digital:MCP42010 Potentiometer_Digital:MCP42050 Potentiometer_Digital:MCP42100 @@ -13306,12 +14802,20 @@ Power_Management:AAT4610BIGV-1-T1 Power_Management:AAT4610BIGV-T1 Power_Management:AAT4616IGV-1-T1 Power_Management:AAT4616IGV-T1 +Power_Management:ADM1270ACPZ +Power_Management:ADM1270ARQZ Power_Management:AP2161W Power_Management:AP2171W Power_Management:AP22804AW5 Power_Management:AP22804BW5 Power_Management:AP22814AW5 Power_Management:AP22814BW5 +Power_Management:AP22816AKEWT +Power_Management:AP22816BKEWT +Power_Management:AP22817AKEWT +Power_Management:AP22817BKEWT +Power_Management:AP22818AKEWT +Power_Management:AP22818BKEWT Power_Management:AP22913CN4 Power_Management:AUIPS1041R Power_Management:AUIPS1042G @@ -13343,6 +14847,9 @@ Power_Management:AUIR3315S Power_Management:AUIR3316S Power_Management:AUIR3320S Power_Management:AUIR33402S +Power_Management:BD2222G +Power_Management:BD2242G +Power_Management:BD2243G Power_Management:BD48ExxG Power_Management:BD48KxxG Power_Management:BD48LxxG @@ -13377,8 +14884,11 @@ Power_Management:BTS6143D Power_Management:BTS6163D Power_Management:BTS6200-1EJA Power_Management:BTS7004-1EPP +Power_Management:BTS711L1 +Power_Management:BTS712N1 Power_Management:BTS716G Power_Management:BTS716GB +Power_Management:BTS721L1 Power_Management:BTS724G Power_Management:CAP002DG Power_Management:CAP003DG @@ -13399,6 +14909,9 @@ Power_Management:CAP019DG Power_Management:CAP200DG Power_Management:CAP300DG Power_Management:DS1210 +Power_Management:EPC23102 +Power_Management:EPC23103 +Power_Management:EPC23104 Power_Management:FPF2000 Power_Management:FPF2001 Power_Management:FPF2002 @@ -13431,6 +14944,12 @@ Power_Management:LM5050-1 Power_Management:LM5050-2 Power_Management:LM5051 Power_Management:LM5060 +Power_Management:LM50672NPAR +Power_Management:LM5067MM-1 +Power_Management:LM5067MM-2 +Power_Management:LM5067MMX-2 +Power_Management:LM5067MWX-1 +Power_Management:LM66100DCK Power_Management:LM74700 Power_Management:LMG3410 Power_Management:LMG5200 @@ -13467,17 +14986,30 @@ Power_Management:LTC4417HGN Power_Management:LTC4417HUF Power_Management:LTC4417IGN Power_Management:LTC4417IUF +Power_Management:MAX14919xUP Power_Management:MAX8586 +Power_Management:MAX9611 +Power_Management:MAX9612 +Power_Management:MIC2007YM6 +Power_Management:MIC2008YM6 +Power_Management:MIC2017YM6 +Power_Management:MIC2018YM6 Power_Management:MIC2025-1YM Power_Management:MIC2025-1YMM Power_Management:MIC2025-2YM Power_Management:MIC2025-2YMM -Power_Management:MIC2026-1BM Power_Management:MIC2026-1BN -Power_Management:MIC2026-2BM +Power_Management:MIC2026-1xM Power_Management:MIC2026-2BN +Power_Management:MIC2026-2xM +Power_Management:MIC2090-1YM5 +Power_Management:MIC2090-2YM5 +Power_Management:MIC2091-1YM5 +Power_Management:MIC2091-2YM5 +Power_Management:MIC2544-2YM Power_Management:MIC2587-1 Power_Management:MIC2587R-1 +Power_Management:NIS5420MTxTXG Power_Management:NPC45560-H Power_Management:NPC45560-L Power_Management:PD70224 @@ -13488,6 +15020,7 @@ Power_Management:RT9742BGJ5F Power_Management:RT9742BNGJ5F Power_Management:SN6505ADBV Power_Management:SN6505BDBV +Power_Management:SN6507DGQ Power_Management:STM6600 Power_Management:STM6601 Power_Management:SiP32431DR3 @@ -13498,7 +15031,12 @@ Power_Management:TLE8104E Power_Management:TPS2041B Power_Management:TPS2042D Power_Management:TPS2044D +Power_Management:TPS2051CDBV Power_Management:TPS2054D +Power_Management:TPS2065CDBV +Power_Management:TPS2065CDBVx-2 +Power_Management:TPS2069CDBV +Power_Management:TPS2116DRL Power_Management:TPS22810DRV Power_Management:TPS22917DBV Power_Management:TPS22929D @@ -13524,19 +15062,31 @@ Power_Management:UCC39002D Power_Protection:CDNBS08-SLVU2.8-4 Power_Protection:CDSOT236-0504C Power_Protection:CM1213A-01SO +Power_Protection:CM1624 +Power_Protection:D3V3X8U9LP3810 Power_Protection:D3V3XA4B10LP Power_Protection:DT1240A-08LP3810 Power_Protection:ECMF02-2AMX6 +Power_Protection:ECMF04-4HSWM10 Power_Protection:EMI2121MTTAG Power_Protection:EMI8132 Power_Protection:ESD224DQA +Power_Protection:ESDA14V2SC5 +Power_Protection:ESDA5V3L +Power_Protection:ESDA5V3SC5 Power_Protection:ESDA6V1-5SC6 Power_Protection:ESDA6V1BC6 +Power_Protection:ESDA6V1SC5 Power_Protection:ESDLC5V0PB8 Power_Protection:IP3319CX6 +Power_Protection:IP4234CZ6 +Power_Protection:IP4251CZ8-4-TTL Power_Protection:IP4252CZ12 Power_Protection:IP4252CZ16 Power_Protection:IP4252CZ8 +Power_Protection:IP4252CZ8-4-TTL +Power_Protection:IP4253CZ8-4-TTL +Power_Protection:IP4254CZ8-4-TTL Power_Protection:NCP349MN Power_Protection:NCP349MNAE Power_Protection:NCP349MNAM @@ -13579,18 +15129,50 @@ Power_Protection:SP0505BAJT Power_Protection:SP7538P Power_Protection:SRV05-4 Power_Protection:SZNUP2105L -Power_Protection:TPD2E2U06 +Power_Protection:TBU-CA-025-050-WH +Power_Protection:TBU-CA-025-100-WH +Power_Protection:TBU-CA-025-200-WH +Power_Protection:TBU-CA-025-300-WH +Power_Protection:TBU-CA-025-500-WH +Power_Protection:TBU-CA-040-050-WH +Power_Protection:TBU-CA-040-100-WH +Power_Protection:TBU-CA-040-200-WH +Power_Protection:TBU-CA-040-300-WH +Power_Protection:TBU-CA-040-500-WH +Power_Protection:TBU-CA-050-050-WH +Power_Protection:TBU-CA-050-100-WH +Power_Protection:TBU-CA-050-200-WH +Power_Protection:TBU-CA-050-300-WH +Power_Protection:TBU-CA-050-500-WH +Power_Protection:TBU-CA-065-050-WH +Power_Protection:TBU-CA-065-100-WH +Power_Protection:TBU-CA-065-200-WH +Power_Protection:TBU-CA-065-300-WH +Power_Protection:TBU-CA-065-500-WH +Power_Protection:TBU-CA-085-050-WH +Power_Protection:TBU-CA-085-100-WH +Power_Protection:TBU-CA-085-200-WH +Power_Protection:TBU-CA-085-300-WH +Power_Protection:TBU-CA-085-500-WH +Power_Protection:TPD1E05U06DPY +Power_Protection:TPD1E05U06DYA +Power_Protection:TPD2E2U06DCK +Power_Protection:TPD2E2U06DRL Power_Protection:TPD2EUSB30 Power_Protection:TPD2EUSB30A Power_Protection:TPD2S017 Power_Protection:TPD3E001DRLR +Power_Protection:TPD3F303DPV Power_Protection:TPD3S014 Power_Protection:TPD3S044 Power_Protection:TPD4E02B04DQA +Power_Protection:TPD4E05U06DQA Power_Protection:TPD4EUSB30 Power_Protection:TPD4S014 Power_Protection:TPD4S1394 +Power_Protection:TPD6E05U06RVZ Power_Protection:TPD6F003 +Power_Protection:TPD6S300A Power_Protection:TPD8F003 Power_Protection:TVS0500DRV Power_Protection:TVS1400DRV @@ -13625,6 +15207,12 @@ Power_Supervisor:CAT811RTBI-GT3 Power_Supervisor:CAT811STBI-GT3 Power_Supervisor:CAT811TTBI-GT3 Power_Supervisor:CAT811ZTBI-GT3 +Power_Supervisor:DIO705 +Power_Supervisor:DIO706 +Power_Supervisor:DIO706J +Power_Supervisor:DIO706R +Power_Supervisor:DIO706S +Power_Supervisor:DIO706T Power_Supervisor:LM3880 Power_Supervisor:LM809 Power_Supervisor:LM810 @@ -13637,8 +15225,15 @@ Power_Supervisor:MAX6371 Power_Supervisor:MAX6372 Power_Supervisor:MAX6373 Power_Supervisor:MAX6374 +Power_Supervisor:MAX690ACSA +Power_Supervisor:MAX690xPA Power_Supervisor:MAX691xPE Power_Supervisor:MAX691xWE +Power_Supervisor:MAX692ACSA +Power_Supervisor:MAX692xPA +Power_Supervisor:MAX694xPA +Power_Supervisor:MAX802LCSA +Power_Supervisor:MAX805LCSA Power_Supervisor:MAX811LEUS-T Power_Supervisor:MAX811MEUS-T Power_Supervisor:MAX811REUS-T @@ -13683,12 +15278,14 @@ Power_Supervisor:TCM810 Power_Supervisor:TL7702A Power_Supervisor:TL7702B Power_Supervisor:TL7705A -Power_Supervisor:TL7705ACPSR +Power_Supervisor:TL7705AxPS Power_Supervisor:TL7705B Power_Supervisor:TL7709A Power_Supervisor:TL7712A Power_Supervisor:TL7715A Power_Supervisor:TL7733B +Power_Supervisor:TLV810EA29DBZ +Power_Supervisor:TPS3430WDRC Power_Supervisor:TPS3702 Power_Supervisor:TPS3808DBV Power_Supervisor:TPS3831 @@ -13707,10 +15304,22 @@ Reference_Current:LT3092xTS8 Reference_Current:PSSI2021SAY Reference_Current:REF200AU Reference_Voltage:AD586 +Reference_Voltage:ADR1399KEZ +Reference_Voltage:ADR1399KHZ Reference_Voltage:ADR420ARMZ Reference_Voltage:ADR421ARMZ Reference_Voltage:ADR423ARMZ Reference_Voltage:ADR425ARMZ +Reference_Voltage:ADR440ARMZ +Reference_Voltage:ADR440xRZ +Reference_Voltage:ADR441ARMZ +Reference_Voltage:ADR441xRZ +Reference_Voltage:ADR443ARMZ +Reference_Voltage:ADR443xRZ +Reference_Voltage:ADR444ARMZ +Reference_Voltage:ADR444xRZ +Reference_Voltage:ADR445ARMZ +Reference_Voltage:ADR445xRZ Reference_Voltage:ADR4520 Reference_Voltage:ADR4525 Reference_Voltage:ADR4530 @@ -13775,6 +15384,9 @@ Reference_Voltage:LM4132xMF-2.5 Reference_Voltage:LM4132xMF-3.0 Reference_Voltage:LM4132xMF-3.3 Reference_Voltage:LM4132xMF-4.1 +Reference_Voltage:LT1009CMS8 +Reference_Voltage:LT1009xS8 +Reference_Voltage:LT1009xZ Reference_Voltage:LT1019xN8 Reference_Voltage:LT1461AxS8-2.5 Reference_Voltage:LT1461AxS8-3 @@ -13821,10 +15433,62 @@ Reference_Voltage:LT6657BHMS8-2.5 Reference_Voltage:LT6657BHMS8-3 Reference_Voltage:LT6657BHMS8-4.096 Reference_Voltage:LT6657BHMS8-5 +Reference_Voltage:MAX6001 +Reference_Voltage:MAX6002 +Reference_Voltage:MAX6003 +Reference_Voltage:MAX6004 +Reference_Voltage:MAX6005 Reference_Voltage:MAX6035xSA25 Reference_Voltage:MAX6035xxUR25 Reference_Voltage:MAX6035xxUR30 Reference_Voltage:MAX6035xxUR50 +Reference_Voltage:MAX6070AAUT12+T +Reference_Voltage:MAX6070AAUT18+T +Reference_Voltage:MAX6070AAUT18V+T +Reference_Voltage:MAX6070AAUT21+T +Reference_Voltage:MAX6070AAUT25+T +Reference_Voltage:MAX6070AAUT30+T +Reference_Voltage:MAX6070AAUT33+T +Reference_Voltage:MAX6070AAUT33V+T +Reference_Voltage:MAX6070AAUT41+T +Reference_Voltage:MAX6070AAUT50+T +Reference_Voltage:MAX6070AAUT50V+T +Reference_Voltage:MAX6070BAUT12+T +Reference_Voltage:MAX6070BAUT12V+T +Reference_Voltage:MAX6070BAUT18+T +Reference_Voltage:MAX6070BAUT21+T +Reference_Voltage:MAX6070BAUT21V+T +Reference_Voltage:MAX6070BAUT25+T +Reference_Voltage:MAX6070BAUT25V+T +Reference_Voltage:MAX6070BAUT30+T +Reference_Voltage:MAX6070BAUT33+T +Reference_Voltage:MAX6070BAUT33V+T +Reference_Voltage:MAX6070BAUT41+T +Reference_Voltage:MAX6070BAUT41V+T +Reference_Voltage:MAX6070BAUT50+T +Reference_Voltage:MAX6070BAUT50V+T +Reference_Voltage:MAX6070DAUT12V+T +Reference_Voltage:MAX6070DAUT25V+T +Reference_Voltage:MAX6070DAUT30V+T +Reference_Voltage:MAX6070DAUT41V+T +Reference_Voltage:MAX6071AAUT12+T +Reference_Voltage:MAX6071AAUT18+T +Reference_Voltage:MAX6071AAUT21+T +Reference_Voltage:MAX6071AAUT25+T +Reference_Voltage:MAX6071AAUT30+T +Reference_Voltage:MAX6071AAUT30V+T +Reference_Voltage:MAX6071AAUT33+T +Reference_Voltage:MAX6071AAUT41+T +Reference_Voltage:MAX6071AAUT50+T +Reference_Voltage:MAX6071BAUT12+T +Reference_Voltage:MAX6071BAUT18+T +Reference_Voltage:MAX6071BAUT21+T +Reference_Voltage:MAX6071BAUT25+T +Reference_Voltage:MAX6071BAUT25V+T +Reference_Voltage:MAX6071BAUT30+T +Reference_Voltage:MAX6071BAUT33+T +Reference_Voltage:MAX6071BAUT41+T +Reference_Voltage:MAX6071BAUT50+T Reference_Voltage:MAX6100 Reference_Voltage:MAX6101 Reference_Voltage:MAX6102 @@ -13917,6 +15581,19 @@ Reference_Voltage:REF3225AMDBVREP Reference_Voltage:REF3230AMDBVREP Reference_Voltage:REF3233AMDBVREP Reference_Voltage:REF3240AMDBVREP +Reference_Voltage:REF35102QDBVR +Reference_Voltage:REF35120QDBVR +Reference_Voltage:REF35125QDBVR +Reference_Voltage:REF35160QDBVR +Reference_Voltage:REF35170QDBVR +Reference_Voltage:REF35180QDBVR +Reference_Voltage:REF35205QDBVR +Reference_Voltage:REF35250QDBVR +Reference_Voltage:REF35300QDBVR +Reference_Voltage:REF35330QDBVR +Reference_Voltage:REF35360QDBVR +Reference_Voltage:REF35409QDBVR +Reference_Voltage:REF35500QDBVR Reference_Voltage:REF5010AD Reference_Voltage:REF5010ADGK Reference_Voltage:REF5010ID @@ -14233,6 +15910,8 @@ Regulator_Linear:AP2204RA-3.3 Regulator_Linear:AP2204RA-5.0 Regulator_Linear:AP2204RB-3.3 Regulator_Linear:AP2204RB-5.0 +Regulator_Linear:AP22615AWU +Regulator_Linear:AP22615BWU Regulator_Linear:AP7361C-10E Regulator_Linear:AP7361C-12E Regulator_Linear:AP7361C-15E @@ -14248,6 +15927,10 @@ Regulator_Linear:AP7370-30FDC Regulator_Linear:AP7370-33FDC Regulator_Linear:AP7370-36FDC Regulator_Linear:AP7370-50FDC +Regulator_Linear:AP7381-28SA-7 +Regulator_Linear:AP7381-33SA-7 +Regulator_Linear:AP7381-50SA-7 +Regulator_Linear:AP7381-70SA-7 Regulator_Linear:AP7384-28SA Regulator_Linear:AP7384-28V Regulator_Linear:AP7384-28Y @@ -14940,6 +16623,8 @@ Regulator_Linear:LT3010 Regulator_Linear:LT3010-5 Regulator_Linear:LT3011xDD Regulator_Linear:LT3011xMSE +Regulator_Linear:LT3014xDD +Regulator_Linear:LT3014xS5 Regulator_Linear:LT3015Q Regulator_Linear:LT3015xQ-12 Regulator_Linear:LT3015xQ-15 @@ -14952,14 +16637,18 @@ Regulator_Linear:LT3032-12 Regulator_Linear:LT3032-15 Regulator_Linear:LT3032-3.3 Regulator_Linear:LT3032-5 +Regulator_Linear:LT3033xUDC Regulator_Linear:LT3042xMSE +Regulator_Linear:LT3045xDD Regulator_Linear:LT3045xMSE Regulator_Linear:LT3080xDD Regulator_Linear:LT3080xMS8E Regulator_Linear:LT3080xQ Regulator_Linear:LT3080xST Regulator_Linear:LT3080xT +Regulator_Linear:LT3091xT7 Regulator_Linear:LT3093xMSE +Regulator_Linear:LT3094xDD Regulator_Linear:LT3094xMSE Regulator_Linear:LTC3026-1 Regulator_Linear:MAX1615xUK @@ -14967,6 +16656,10 @@ Regulator_Linear:MAX1616xUK Regulator_Linear:MAX1658ESA Regulator_Linear:MAX1659ESA Regulator_Linear:MAX16910 +Regulator_Linear:MAX38908xTD +Regulator_Linear:MAX38909xTD +Regulator_Linear:MAX38911ATA+ +Regulator_Linear:MAX38912ATA+ Regulator_Linear:MAX5092AATE Regulator_Linear:MAX5092BATE Regulator_Linear:MAX5093AATE @@ -15130,6 +16823,18 @@ Regulator_Linear:MCP1804x-C002xMT Regulator_Linear:MCP1804x-C002xOT Regulator_Linear:MCP1825S Regulator_Linear:MCP1826S +Regulator_Linear:ME6211C10M5 +Regulator_Linear:ME6211C12M5 +Regulator_Linear:ME6211C15M5 +Regulator_Linear:ME6211C18M5 +Regulator_Linear:ME6211C21M5 +Regulator_Linear:ME6211C25M5 +Regulator_Linear:ME6211C27M5 +Regulator_Linear:ME6211C28M5 +Regulator_Linear:ME6211C29M5 +Regulator_Linear:ME6211C30M5 +Regulator_Linear:ME6211C33M5 +Regulator_Linear:ME6211C50M5 Regulator_Linear:MIC29152WT Regulator_Linear:MIC29152WU Regulator_Linear:MIC29153WT @@ -15186,6 +16891,7 @@ Regulator_Linear:MIC5317-2.5xM5 Regulator_Linear:MIC5317-2.8xM5 Regulator_Linear:MIC5317-3.0xM5 Regulator_Linear:MIC5317-3.3xM5 +Regulator_Linear:MIC5350 Regulator_Linear:MIC5353-1.8YMT Regulator_Linear:MIC5353-2.5YMT Regulator_Linear:MIC5353-2.6YMT @@ -15296,6 +17002,9 @@ Regulator_Linear:NCV8114BSN180T1G Regulator_Linear:NCV8114BSN280T1G Regulator_Linear:NCV8114BSN300T1G Regulator_Linear:NCV8114BSN330T1G +Regulator_Linear:NDP6802SF-33 +Regulator_Linear:NDP6802SF-50 +Regulator_Linear:NDP6802SF-A2 Regulator_Linear:NVC8674DS120 Regulator_Linear:NVC8674DS50 Regulator_Linear:OM1323_TO220 @@ -15428,6 +17137,8 @@ Regulator_Linear:TLV70237_WSON6 Regulator_Linear:TLV70242PDSE Regulator_Linear:TLV70245_SOT23-5 Regulator_Linear:TLV702475_SOT23-5 +Regulator_Linear:TLV7113318DDSE +Regulator_Linear:TLV7113333DDSE Regulator_Linear:TLV71209_SOT23-5 Regulator_Linear:TLV71210_SOT23-5 Regulator_Linear:TLV71211_SOT23-5 @@ -15442,6 +17153,7 @@ Regulator_Linear:TLV713285PDBV Regulator_Linear:TLV71328PDBV Regulator_Linear:TLV71330PDBV Regulator_Linear:TLV71333PDBV +Regulator_Linear:TLV71x_WSON-6 Regulator_Linear:TLV73310PDBV Regulator_Linear:TLV73311PDBV Regulator_Linear:TLV73312PDBV @@ -15498,6 +17210,8 @@ Regulator_Linear:TLV75733PDRV Regulator_Linear:TLV75740PDRV Regulator_Linear:TLV75801PDBV Regulator_Linear:TLV75801PDRV +Regulator_Linear:TLV76133DCY +Regulator_Linear:TLV76150DCY Regulator_Linear:TLV76701DRVx Regulator_Linear:TLV76701QWDRBxQ1 Regulator_Linear:TLV76708DRVx @@ -15542,6 +17256,9 @@ Regulator_Linear:TPS72201 Regulator_Linear:TPS72215 Regulator_Linear:TPS72216 Regulator_Linear:TPS72218 +Regulator_Linear:TPS72301DBV +Regulator_Linear:TPS72301DDC +Regulator_Linear:TPS72325DBV Regulator_Linear:TPS73018DBV Regulator_Linear:TPS730285DBV Regulator_Linear:TPS73101DBV @@ -15566,6 +17283,8 @@ Regulator_Linear:TPS73632DBV Regulator_Linear:TPS73633DBV Regulator_Linear:TPS73643DBV Regulator_Linear:TPS74401_VQFN +Regulator_Linear:TPS74801AWDRC +Regulator_Linear:TPS74801DRC Regulator_Linear:TPS75005RGW Regulator_Linear:TPS76301 Regulator_Linear:TPS76316 @@ -15643,10 +17362,19 @@ Regulator_Linear:TPS7A0530PDBZ Regulator_Linear:TPS7A0531PDBV Regulator_Linear:TPS7A0533PDBV Regulator_Linear:TPS7A0533PDBZ +Regulator_Linear:TPS7A20xxxDQN +Regulator_Linear:TPS7A3301RGW +Regulator_Linear:TPS7A39 +Regulator_Linear:TPS7A4101DGN +Regulator_Linear:TPS7A4701xRGW Regulator_Linear:TPS7A7001DDA Regulator_Linear:TPS7A7200RGW Regulator_Linear:TPS7A90 Regulator_Linear:TPS7A91 +Regulator_Linear:UA78M05QDCYRQ1 +Regulator_Linear:UA78M08QDCYRQ1 +Regulator_Linear:UA78M10QDCYRQ1 +Regulator_Linear:UA78M33QDCYRQ1 Regulator_Linear:XC6206PxxxMR Regulator_Linear:XC6210B332MR Regulator_Linear:XC6220B331MR @@ -15670,12 +17398,25 @@ Regulator_SwitchedCapacitor:LT1054 Regulator_SwitchedCapacitor:LT1054L Regulator_SwitchedCapacitor:LT1054xSW Regulator_SwitchedCapacitor:LTC1044 +Regulator_SwitchedCapacitor:LTC1502xMS8-3.3 +Regulator_SwitchedCapacitor:LTC1502xS8-3.3 +Regulator_SwitchedCapacitor:LTC1503CMS8-1.8 +Regulator_SwitchedCapacitor:LTC1503CMS8-2 +Regulator_SwitchedCapacitor:LTC1503xS8-1.8 +Regulator_SwitchedCapacitor:LTC1503xS8-2 +Regulator_SwitchedCapacitor:LTC1751 Regulator_SwitchedCapacitor:LTC1754 +Regulator_SwitchedCapacitor:LTC3260xDE +Regulator_SwitchedCapacitor:LTC3260xMSE Regulator_SwitchedCapacitor:LTC660 Regulator_SwitchedCapacitor:MAX1044 Regulator_SwitchedCapacitor:RT9361AxE Regulator_SwitchedCapacitor:RT9361BxE Regulator_SwitchedCapacitor:TPS60151DRV +Regulator_SwitchedCapacitor:TPS60400DBV +Regulator_SwitchedCapacitor:TPS60401DBV +Regulator_SwitchedCapacitor:TPS60402DBV +Regulator_SwitchedCapacitor:TPS60403DBV Regulator_SwitchedCapacitor:TPS60500DGS Regulator_SwitchedCapacitor:TPS60501DGS Regulator_SwitchedCapacitor:TPS60502DGS @@ -15723,10 +17464,15 @@ Regulator_Switching:ADuM6000 Regulator_Switching:AOZ1280CI Regulator_Switching:AOZ1282CI Regulator_Switching:AOZ1282CI-1 +Regulator_Switching:AOZ6663DI +Regulator_Switching:AOZ6663DI-01 Regulator_Switching:AP3012 Regulator_Switching:AP3211K Regulator_Switching:AP3402 +Regulator_Switching:AP3441SHE +Regulator_Switching:AP62150WU Regulator_Switching:AP62150Z6 +Regulator_Switching:AP62250WU Regulator_Switching:AP62250Z6 Regulator_Switching:AP62300TWU Regulator_Switching:AP62300WU @@ -15799,6 +17545,8 @@ Regulator_Switching:GL2576-5.0TB5T Regulator_Switching:GL2576-ASF8DR Regulator_Switching:GL2576-ATA5R Regulator_Switching:GL2576-ATB5T +Regulator_Switching:HT7463A +Regulator_Switching:HT7463B Regulator_Switching:ISL8117FRZ Regulator_Switching:ISL8117FVEZ Regulator_Switching:KA5H02659RN @@ -15813,10 +17561,15 @@ Regulator_Switching:KA5M0265RTU Regulator_Switching:KA5M0265RYDTU Regulator_Switching:KA5M0280RTU Regulator_Switching:KA5M0280RYDTU +Regulator_Switching:L4962-A +Regulator_Switching:L4962E-A +Regulator_Switching:L4962EH-A Regulator_Switching:L5973D Regulator_Switching:L7980A Regulator_Switching:LD7575 -Regulator_Switching:LGS6302 +Regulator_Switching:LGS5116B +Regulator_Switching:LGS5145 +Regulator_Switching:LGS6302B5 Regulator_Switching:LM22676MR-5 Regulator_Switching:LM22676MR-ADJ Regulator_Switching:LM22678TJ-5 @@ -15993,6 +17746,8 @@ Regulator_Switching:LMR33630CDDA Regulator_Switching:LMR33640ADDA Regulator_Switching:LMR33640DDDA Regulator_Switching:LMR36510ADDA +Regulator_Switching:LMR50410 +Regulator_Switching:LMR51430 Regulator_Switching:LMR62014XMF Regulator_Switching:LMR62421XMF Regulator_Switching:LMR62421XSD @@ -16152,6 +17907,8 @@ Regulator_Switching:LT3430-1 Regulator_Switching:LT3439 Regulator_Switching:LT3471 Regulator_Switching:LT3472 +Regulator_Switching:LT3483AxS6 +Regulator_Switching:LT3483xS6 Regulator_Switching:LT3514xUFD Regulator_Switching:LT3580xDD Regulator_Switching:LT3580xMS8E @@ -16162,15 +17919,19 @@ Regulator_Switching:LT3757EDD Regulator_Switching:LT3757EMSE Regulator_Switching:LT3988 Regulator_Switching:LT8303 +Regulator_Switching:LT8306 Regulator_Switching:LT8610 Regulator_Switching:LT8610AC Regulator_Switching:LT8610AC-1 +Regulator_Switching:LT8705AxFE Regulator_Switching:LTC1436A Regulator_Switching:LTC1436A-PLL Regulator_Switching:LTC1437A Regulator_Switching:LTC1878EMS8 Regulator_Switching:LTC3105xDD Regulator_Switching:LTC3105xMS +Regulator_Switching:LTC3245xDE +Regulator_Switching:LTC3245xMSE Regulator_Switching:LTC3406AES5 Regulator_Switching:LTC3406B-2ES5 Regulator_Switching:LTC3406BES5-1.2 @@ -16187,24 +17948,33 @@ Regulator_Switching:LTC3525-3.3 Regulator_Switching:LTC3525-5 Regulator_Switching:LTC3525D-3.3 Regulator_Switching:LTC3525L-3 -Regulator_Switching:LTC3630ADHC -Regulator_Switching:LTC3630AMSE -Regulator_Switching:LTC3630DHC -Regulator_Switching:LTC3630MSE -Regulator_Switching:LTC3638 -Regulator_Switching:LTC3639 +Regulator_Switching:LTC3561EDD +Regulator_Switching:LTC3630AxDHC +Regulator_Switching:LTC3630AxMSE +Regulator_Switching:LTC3630xDHC +Regulator_Switching:LTC3630xMSE +Regulator_Switching:LTC3638xMSE +Regulator_Switching:LTC3639xMSE Regulator_Switching:LTC3886 -Regulator_Switching:LTC7138 +Regulator_Switching:LTC7138xMSE +Regulator_Switching:LTM4626 Regulator_Switching:LTM4637xV Regulator_Switching:LTM4637xY +Regulator_Switching:LTM4638 +Regulator_Switching:LTM4657 Regulator_Switching:LTM4668 Regulator_Switching:LTM4668A +Regulator_Switching:LTM4671 +Regulator_Switching:LTM8049 Regulator_Switching:LTM8063 Regulator_Switching:LV2862XDDC Regulator_Switching:LV2862YDDC Regulator_Switching:MAX15062A Regulator_Switching:MAX15062B Regulator_Switching:MAX15062C +Regulator_Switching:MAX1522 +Regulator_Switching:MAX1523 +Regulator_Switching:MAX1524 Regulator_Switching:MAX17501AxTB Regulator_Switching:MAX17501BxTB Regulator_Switching:MAX17501ExTB @@ -16225,6 +17995,7 @@ Regulator_Switching:MAX5035DUPA Regulator_Switching:MAX5035DUSA Regulator_Switching:MAX5035EUSA Regulator_Switching:MAX777L +Regulator_Switching:MAX77827AEFD Regulator_Switching:MAX778L Regulator_Switching:MAX779L Regulator_Switching:MC33063AD @@ -16232,26 +18003,28 @@ Regulator_Switching:MC33063AP Regulator_Switching:MC33063MNTXG Regulator_Switching:MC34063AD Regulator_Switching:MC34063AP -Regulator_Switching:MCP16301 -Regulator_Switching:MCP16301H -Regulator_Switching:MCP16311MNY -Regulator_Switching:MCP16311MS -Regulator_Switching:MCP16312MNY -Regulator_Switching:MCP16312MS -Regulator_Switching:MCP16331CH -Regulator_Switching:MCP16331MN -Regulator_Switching:MCP1640BCH -Regulator_Switching:MCP1640BMC -Regulator_Switching:MCP1640CCH -Regulator_Switching:MCP1640CH -Regulator_Switching:MCP1640CMC -Regulator_Switching:MCP1640DCH -Regulator_Switching:MCP1640DMC -Regulator_Switching:MCP1640MC -Regulator_Switching:MCP1650 -Regulator_Switching:MCP1651 -Regulator_Switching:MCP1652 -Regulator_Switching:MCP1653 +Regulator_Switching:MCP1623x-xCHY +Regulator_Switching:MCP1623x-xMC +Regulator_Switching:MCP16301Hx-xCH +Regulator_Switching:MCP16301x-xCH +Regulator_Switching:MCP16311x-xMNY +Regulator_Switching:MCP16311x-xMS +Regulator_Switching:MCP16312x-xMNY +Regulator_Switching:MCP16312x-xMS +Regulator_Switching:MCP16331x-xCH +Regulator_Switching:MCP16331x-xMNY +Regulator_Switching:MCP1640Bx-xCHY +Regulator_Switching:MCP1640Bx-xMC +Regulator_Switching:MCP1640Cx-xCHY +Regulator_Switching:MCP1640Cx-xMC +Regulator_Switching:MCP1640Dx-xCHY +Regulator_Switching:MCP1640Dx-xMC +Regulator_Switching:MCP1640x-xCHY +Regulator_Switching:MCP1640x-xMC +Regulator_Switching:MCP1650x-xMC +Regulator_Switching:MCP1651x-xMC +Regulator_Switching:MCP1652x-xMC +Regulator_Switching:MCP1653x-xUN Regulator_Switching:MIC2177 Regulator_Switching:MIC2177-3.3 Regulator_Switching:MIC2177-5.0 @@ -16259,6 +18032,7 @@ Regulator_Switching:MIC2178 Regulator_Switching:MIC2178-3.3 Regulator_Switching:MIC2178-5.0 Regulator_Switching:MIC2207 +Regulator_Switching:MIC2253 Regulator_Switching:MIC2290 Regulator_Switching:MIC23050-4YML Regulator_Switching:MIC23050-CYML @@ -16271,6 +18045,7 @@ Regulator_Switching:MP171GJ Regulator_Switching:MP171GS Regulator_Switching:MP2303ADN Regulator_Switching:MP2303ADP +Regulator_Switching:MPM3550EGLE Regulator_Switching:MT3608 Regulator_Switching:MUN12AD01-SH Regulator_Switching:MUN12AD03-SH @@ -16427,6 +18202,18 @@ Regulator_Switching:R-78S3.3-0.1 Regulator_Switching:SC33063AD Regulator_Switching:SC34063AP Regulator_Switching:SC4503TSK +Regulator_Switching:SIC431A +Regulator_Switching:SIC431B +Regulator_Switching:SIC431C +Regulator_Switching:SIC431D +Regulator_Switching:SIC437A +Regulator_Switching:SIC437B +Regulator_Switching:SIC437C +Regulator_Switching:SIC437D +Regulator_Switching:SIC438A +Regulator_Switching:SIC438B +Regulator_Switching:SIC438C +Regulator_Switching:SIC438D Regulator_Switching:ST1S10PHR Regulator_Switching:ST1S10PUR Regulator_Switching:ST1S12XX @@ -16451,14 +18238,22 @@ Regulator_Switching:TDN_5-4815WISM Regulator_Switching:TDN_5-4819WISM Regulator_Switching:TL497 Regulator_Switching:TL497A +Regulator_Switching:TL5001 +Regulator_Switching:TL5001A Regulator_Switching:TLV61046ADB +Regulator_Switching:TLV61070ADBV +Regulator_Switching:TLV61225DC Regulator_Switching:TLV62080DSGx Regulator_Switching:TLV62084ADSGx Regulator_Switching:TLV62084DSGx Regulator_Switching:TLV62095RGTx +Regulator_Switching:TLV62565DBVx +Regulator_Switching:TLV62566DBVx +Regulator_Switching:TLV62568ADRL Regulator_Switching:TLV62568DBV Regulator_Switching:TLV62568DDC Regulator_Switching:TLV62568DRL +Regulator_Switching:TLV62569ADRL Regulator_Switching:TLV62569DBV Regulator_Switching:TLV62569DDC Regulator_Switching:TLV62569DRL @@ -16648,12 +18443,16 @@ Regulator_Switching:TOP270VG Regulator_Switching:TOP271EG Regulator_Switching:TOP271KG Regulator_Switching:TOP271VG +Regulator_Switching:TOS06-05SIL +Regulator_Switching:TOS06-12SIL Regulator_Switching:TPS51363 Regulator_Switching:TPS5403 Regulator_Switching:TPS54061DRB Regulator_Switching:TPS54202DDC Regulator_Switching:TPS5420D Regulator_Switching:TPS54233 +Regulator_Switching:TPS54260DGQ +Regulator_Switching:TPS54260DRC Regulator_Switching:TPS54302 Regulator_Switching:TPS54308 Regulator_Switching:TPS5430DDA @@ -16664,10 +18463,19 @@ Regulator_Switching:TPS54360DDA Regulator_Switching:TPS54560BDDA Regulator_Switching:TPS560200 Regulator_Switching:TPS562200 +Regulator_Switching:TPS562202 +Regulator_Switching:TPS562202S +Regulator_Switching:TPS562203 +Regulator_Switching:TPS562206 Regulator_Switching:TPS563200 +Regulator_Switching:TPS563202S +Regulator_Switching:TPS563203 +Regulator_Switching:TPS563206 Regulator_Switching:TPS563240DDC +Regulator_Switching:TPS563300 Regulator_Switching:TPS56339DDC Regulator_Switching:TPS565208 +Regulator_Switching:TPS56528DDA Regulator_Switching:TPS568215RNN Regulator_Switching:TPS61040DBV Regulator_Switching:TPS61040DDC @@ -16675,6 +18483,10 @@ Regulator_Switching:TPS61040DRV Regulator_Switching:TPS61041DBV Regulator_Switching:TPS61041DDC Regulator_Switching:TPS61041DRV +Regulator_Switching:TPS61085DGK +Regulator_Switching:TPS61085PW +Regulator_Switching:TPS61089 +Regulator_Switching:TPS610891 Regulator_Switching:TPS61090 Regulator_Switching:TPS61091 Regulator_Switching:TPS61092 @@ -16747,7 +18559,29 @@ Regulator_Switching:TPS62208DBV Regulator_Switching:TPS62821DLC Regulator_Switching:TPS62822DLC Regulator_Switching:TPS62823DLC +Regulator_Switching:TPS628436DRL +Regulator_Switching:TPS628436YKA +Regulator_Switching:TPS628437DRL +Regulator_Switching:TPS628437YKA +Regulator_Switching:TPS628438DRL +Regulator_Switching:TPS628438YKA +Regulator_Switching:TPS62912 +Regulator_Switching:TPS62913 +Regulator_Switching:TPS62932 Regulator_Switching:TPS62933 +Regulator_Switching:TPS62933F +Regulator_Switching:TPS62933O +Regulator_Switching:TPS62933P +Regulator_Switching:TPS62A01ADRL +Regulator_Switching:TPS62A01APDDC +Regulator_Switching:TPS62A01DRL +Regulator_Switching:TPS62A01PDDC +Regulator_Switching:TPS62A02ADRL +Regulator_Switching:TPS62A02APDDC +Regulator_Switching:TPS62A02DRL +Regulator_Switching:TPS62A02NADRL +Regulator_Switching:TPS62A02NDRL +Regulator_Switching:TPS62A02PDDC Regulator_Switching:TPS63000 Regulator_Switching:TPS63000-Q1 Regulator_Switching:TPS63001 @@ -16762,8 +18596,25 @@ Regulator_Switching:TPS65131RGE Regulator_Switching:TPS82130 Regulator_Switching:TPS82140 Regulator_Switching:TPS82150 +Regulator_Switching:TSR0.6-48120WI +Regulator_Switching:TSR0.6-48150WI +Regulator_Switching:TSR0.6-48240WI +Regulator_Switching:TSR0.6-4833WI +Regulator_Switching:TSR0.6-4850WI +Regulator_Switching:TSR0.6-4865WI +Regulator_Switching:TSR0.6-4890WI Regulator_Switching:TSR1-2433E Regulator_Switching:TSR1-2450E +Regulator_Switching:TSR2-24120N +Regulator_Switching:TSR2-2412N +Regulator_Switching:TSR2-24150N +Regulator_Switching:TSR2-2415N +Regulator_Switching:TSR2-2418N +Regulator_Switching:TSR2-2425N +Regulator_Switching:TSR2-2433N +Regulator_Switching:TSR2-2450N +Regulator_Switching:TSR2-2465N +Regulator_Switching:TSR2-2490N Regulator_Switching:TSR_1-2412 Regulator_Switching:TSR_1-24120 Regulator_Switching:TSR_1-2415 @@ -16791,12 +18642,22 @@ Relay:ADW11 Relay:AZ850-x Relay:AZ850P1-x Relay:AZ850P2-x +Relay:AZSR131-1AE-12D +Relay:COTO_3602_Split +Relay:COTO_3650_Split +Relay:COTO_3660_Split Relay:DIPxx-1Axx-11x Relay:DIPxx-1Axx-12x Relay:DIPxx-1Axx-12xD Relay:DIPxx-1Axx-13x Relay:DIPxx-1Cxx-51x Relay:DIPxx-2Axx-21x +Relay:DR-24V +Relay:DR-3V +Relay:DR-5V +Relay:DR-L-3V +Relay:DR-L2-3V_Form1 +Relay:DR-L2-3V_Form2 Relay:EC2-12NU Relay:EC2-12SNU Relay:EC2-12TNU @@ -16890,11 +18751,15 @@ Relay:G2RL-1A-H Relay:G2RL-2 Relay:G2RL-2A Relay:G5LE-1 +Relay:G5NB Relay:G5Q-1 Relay:G5Q-1A Relay:G5V-1 Relay:G5V-2 Relay:G5V-2_Split +Relay:G6A +Relay:G6AK +Relay:G6AU Relay:G6E Relay:G6EU Relay:G6H-2 @@ -16904,6 +18769,7 @@ Relay:G6KU-2 Relay:G6S-2 Relay:G6SK-2 Relay:G6SU-2 +Relay:HF115F-2Z-x4 Relay:HF3-01 Relay:HF3-02 Relay:HF3-03 @@ -16918,6 +18784,8 @@ Relay:HF3-54 Relay:HF3-55 Relay:HF3-56 Relay:HF3-57 +Relay:HK19F-DCxxV-SHC +Relay:HONGFA_HFD2-0xx-x-L2-x Relay:IM00 Relay:IM01 Relay:IM02 @@ -16945,9 +18813,39 @@ Relay:IM45 Relay:IM46 Relay:IM47 Relay:IM48 +Relay:JQC-3FF-005-1H +Relay:JQC-3FF-005-1Z +Relay:JQC-3FF-006-1H +Relay:JQC-3FF-006-1Z +Relay:JQC-3FF-009-1H +Relay:JQC-3FF-009-1Z +Relay:JQC-3FF-012-1H +Relay:JQC-3FF-012-1Z +Relay:JQC-3FF-018-1H +Relay:JQC-3FF-018-1Z +Relay:JQC-3FF-024-1H +Relay:JQC-3FF-024-1Z +Relay:JQC-3FF-048-1H +Relay:JQC-3FF-048-1Z Relay:JW2 Relay:MSxx-1Axx-75 Relay:MSxx-1Bxx-75 +Relay:Panasonic_ALFG1PF09 +Relay:Panasonic_ALFG1PF091 +Relay:Panasonic_ALFG1PF12 +Relay:Panasonic_ALFG1PF121 +Relay:Panasonic_ALFG1PF18 +Relay:Panasonic_ALFG1PF181 +Relay:Panasonic_ALFG1PF24 +Relay:Panasonic_ALFG1PF241 +Relay:Panasonic_ALFG2PF09 +Relay:Panasonic_ALFG2PF091 +Relay:Panasonic_ALFG2PF12 +Relay:Panasonic_ALFG2PF121 +Relay:Panasonic_ALFG2PF18 +Relay:Panasonic_ALFG2PF181 +Relay:Panasonic_ALFG2PF24 +Relay:Panasonic_ALFG2PF241 Relay:RAYEX-L90 Relay:RAYEX-L90A Relay:RAYEX-L90AS @@ -16971,9 +18869,19 @@ Relay:RTE2xFxx Relay:RTE2xxxx Relay:RTE4xxxx Relay:Relay_DPDT +Relay:Relay_DPDT_Latching_1coil +Relay:Relay_DPDT_Latching_2coil +Relay:Relay_DPST-NC Relay:Relay_DPST-NO +Relay:Relay_DPST_Latching_1coil +Relay:Relay_DPST_Latching_2coil Relay:Relay_SPDT +Relay:Relay_SPDT_Latching_1coil +Relay:Relay_SPDT_Latching_2coil +Relay:Relay_SPST-NC Relay:Relay_SPST-NO +Relay:Relay_SPST_Latching_1coil +Relay:Relay_SPST_Latching_2coil Relay:SANYOU_SRD_Form_A Relay:SANYOU_SRD_Form_B Relay:SANYOU_SRD_Form_C @@ -16984,6 +18892,8 @@ Relay:TE_PCH-1xxx2M Relay:TIANBO-HJR-4102-L Relay:UMS05-1A80-75D Relay:UMS05-1A80-75L +Relay:V23072-Cx061-xxx8 +Relay:V23072-Cx062-xxx8 Relay:Y14x-1C-xxDS Relay_SolidState:34.81-7048 Relay_SolidState:34.81-8240 @@ -17005,6 +18915,7 @@ Relay_SolidState:AQH3213A Relay_SolidState:AQH3223 Relay_SolidState:AQH3223A Relay_SolidState:ASSR-1218 +Relay_SolidState:BC2213A Relay_SolidState:CPC1002N Relay_SolidState:CPC1017N Relay_SolidState:CPC1117N @@ -17018,6 +18929,7 @@ Relay_SolidState:FODM3022 Relay_SolidState:FODM3023 Relay_SolidState:FODM3052 Relay_SolidState:FODM3053 +Relay_SolidState:HHG1D-1 Relay_SolidState:LAA110 Relay_SolidState:LBB110 Relay_SolidState:LCC110 @@ -17082,16 +18994,22 @@ RF:CC1200 RF:CC2500 RF:DC4759J5020AHF-1 RF:DC4759J5020AHF-2 +RF:DW1000 +RF:F113 +RF:F115 +RF:F117 RF:HMC394LP4 RF:HMC431 RF:LAT-3 RF:LRPS-2-1 +RF:LTC5507ES6 RF:MAADSS0008 RF:MAAVSS0004 RF:MC12080 RF:MC12093D RF:MICRF112YMM RF:MICRF220AYQS +RF:MRF89XA RF:NRF24L01 RF:NRF24L01_Breakout RF:PAT1220-C-0DB @@ -17109,6 +19027,9 @@ RF:PD4859J5050S2HF RF:RMK-3-451 RF:RMK-5-51 RF:SE5004L +RF:SX1231IMLTRT +RF:SX1261IMLTRT +RF:SX1262IMLTRT RF:SX1272 RF:SX1273 RF:SX1276 @@ -17220,6 +19141,7 @@ RF_Bluetooth:BM78SPPS5NC2 RF_Bluetooth:BTM112 RF_Bluetooth:BTM222 RF_Bluetooth:MOD-nRF8001 +RF_Bluetooth:Microchip_BM83 RF_Bluetooth:RFD77101 RF_Bluetooth:RN42 RF_Bluetooth:RN42N @@ -17227,6 +19149,7 @@ RF_Bluetooth:RN4871 RF_Bluetooth:SPBTLE-RF RF_Bluetooth:SPBTLE-RF0 RF_Bluetooth:nRF8001 +RF_Filter:B3715 RF_Filter:BFCN-1445 RF_Filter:BFCN-1525 RF_Filter:BFCN-152W-75 @@ -17450,13 +19373,25 @@ RF_GPS:RXM-GPS-RM RF_GPS:SAM-M8Q RF_GPS:SIM28ML RF_GPS:ZED-F9P +RF_GPS:ZOE-M8G +RF_GPS:ZOE-M8Q RF_GSM:BC66 RF_GSM:BC95 +RF_GSM:BG95-M1 +RF_GSM:BG95-M2 +RF_GSM:BG95-M3 +RF_GSM:BG95-M4 +RF_GSM:BG95-M5 +RF_GSM:BG95-M6 +RF_GSM:BG95-M8 +RF_GSM:BG95-MF +RF_GSM:LENA-R8001 RF_GSM:M95 RF_GSM:SARA-U201 RF_GSM:SARA-U260 RF_GSM:SARA-U270 RF_GSM:SARA-U280 +RF_GSM:SE150A4 RF_GSM:SIM7020C RF_GSM:SIM7020E RF_GSM:SIM800C @@ -17483,6 +19418,7 @@ RF_Module:DCTR-52DA RF_Module:DCTR-52DAT RF_Module:DWM1000 RF_Module:DWM1001 +RF_Module:DWM3000 RF_Module:E18-MS1-PCB RF_Module:E73-2G4M04S-52810 RF_Module:E73-2G4M04S-52832 @@ -17490,12 +19426,26 @@ RF_Module:ESP-07 RF_Module:ESP-12E RF_Module:ESP-12F RF_Module:ESP-WROOM-02 -RF_Module:ESP32-PICO-D4 +RF_Module:ESP32-C3-DevKitM-1 +RF_Module:ESP32-C3-WROOM-02 +RF_Module:ESP32-C3-WROOM-02U +RF_Module:ESP32-C6-MINI-1 RF_Module:ESP32-S2-WROVER RF_Module:ESP32-S2-WROVER-I +RF_Module:ESP32-S3-MINI-1 +RF_Module:ESP32-S3-MINI-1U +RF_Module:ESP32-S3-WROOM-1 +RF_Module:ESP32-S3-WROOM-2 RF_Module:ESP32-WROOM-32 RF_Module:ESP32-WROOM-32D +RF_Module:ESP32-WROOM-32E +RF_Module:ESP32-WROOM-32E-R2 RF_Module:ESP32-WROOM-32U +RF_Module:ESP32-WROOM-32UE +RF_Module:ESP32-WROOM-32UE-R2 +RF_Module:HT-CT62 +RF_Module:Jadak_Thingmagic_M6e-Nano +RF_Module:MDBT42Q-512K RF_Module:MDBT50Q-1MV2 RF_Module:MDBT50Q-512K RF_Module:MDBT50Q-P1MV2 @@ -17524,12 +19474,15 @@ RF_Module:RFM97W-868S2 RF_Module:RFM97W-915S2 RF_Module:RFM98W-315S2 RF_Module:RFM98W-433S2 +RF_Module:STM32WB5MMG RF_Module:TD1205 RF_Module:TD1208 RF_Module:TR-52DA RF_Module:TR-52DAT RF_Module:TR-72DA RF_Module:TR-72DAT +RF_Module:WEMOS_C3_mini +RF_Module:WEMOS_D1_mini RF_Module:iM880A RF_Module:iM880B RF_NFC:PN5321A3HN_C1xx @@ -17551,6 +19504,7 @@ RF_Switch:ADG918BRM RF_Switch:ADG919BCPZ RF_Switch:ADG919BRMZ RF_Switch:AS179-92LF +RF_Switch:BGS12WN6E6327 RF_Switch:HMC7992 RF_Switch:HMC849A RF_Switch:KSW-2-46 @@ -17582,8 +19536,12 @@ RF_ZigBee:TWE-L-DP-W RF_ZigBee:TWE-L-WX RF_ZigBee:XBee_SMT Security:ATAES132A-SH +Security:ATECC508A-MAHDA +Security:ATECC508A-SSHDA Security:ATECC608A-MAHDA Security:ATECC608A-SSHDA +Security:ATECC608B-MAHDA +Security:ATECC608B-SSHDA Sensor:ADE7758 Sensor:ADE7763xRS Sensor:ADE7953xCP @@ -17599,6 +19557,7 @@ Sensor:INA260 Sensor:LTC2990 Sensor:MAX30102 Sensor:Nuclear-Radiation_Detector +Sensor:RPR-0521RS Sensor:SHT1x Sensor_Audio:ICS-43434 Sensor_Audio:IM69D120 @@ -17607,6 +19566,7 @@ Sensor_Audio:IM73A135V01 Sensor_Audio:MP45DT02 Sensor_Audio:SPH0641LU4H-1 Sensor_Audio:SPH0645LM4H +Sensor_Audio:SPM0687LR5H-1 Sensor_Current:A1363xKTTN-1 Sensor_Current:A1363xKTTN-10 Sensor_Current:A1363xKTTN-2 @@ -17772,7 +19732,9 @@ Sensor_Current:ACS781xLRTR-150U Sensor_Current:CKSR_15-NP Sensor_Current:CKSR_25-NP Sensor_Current:CKSR_50-NP +Sensor_Current:CKSR_50-NP-SP1 Sensor_Current:CKSR_6-NP +Sensor_Current:CKSR_75-NP Sensor_Current:CQ-2063 Sensor_Current:CQ-2064 Sensor_Current:CQ-2065 @@ -17819,6 +19781,7 @@ Sensor_Current:CZ-3813 Sensor_Current:CZ-3814 Sensor_Current:CZ-3815 Sensor_Current:HO120-NP +Sensor_Current:HO128-NP Sensor_Current:HO15-NP Sensor_Current:HO15-NPxSP33 Sensor_Current:HO15-NSM @@ -17855,18 +19818,34 @@ Sensor_Current:IR22771S Sensor_Current:IR2277S Sensor_Current:IR25750L Sensor_Current:LA100-P +Sensor_Current:LA25-NP Sensor_Current:LA25-P Sensor_Current:LA55-P Sensor_Current:LTSR15-NP Sensor_Current:LTSR25-NP Sensor_Current:LTSR6-NP +Sensor_Current:MCA1101-20-3 +Sensor_Current:MCA1101-20-5 +Sensor_Current:MCA1101-5-3 +Sensor_Current:MCA1101-5-5 +Sensor_Current:MCA1101-50-3 +Sensor_Current:MCA1101-50-5 +Sensor_Current:MCA1101-65-5 +Sensor_Distance:TMF8820 +Sensor_Distance:TMF8821 +Sensor_Distance:TMF8828 +Sensor_Distance:VL53L0CXV0DH1 Sensor_Distance:VL53L1CXV0FY1 +Sensor_Energy:ATM90E26-YU Sensor_Energy:INA219AxD Sensor_Energy:INA219AxDCN Sensor_Energy:INA219BxD Sensor_Energy:INA219BxDCN Sensor_Energy:INA226 +Sensor_Energy:INA228 Sensor_Energy:INA233 +Sensor_Energy:INA237 +Sensor_Energy:INA238 Sensor_Energy:LTC4151xMS Sensor_Energy:MCP39F521 Sensor_Energy:PAC1931x-xJ6CX @@ -17914,6 +19893,7 @@ Sensor_Gas:MQ-6 Sensor_Gas:MiCS-5524 Sensor_Gas:SCD40-D-R2 Sensor_Gas:SCD41-D-R2 +Sensor_Gas:TGS-5141 Sensor_Humidity:ENS210 Sensor_Humidity:HDC1080 Sensor_Humidity:HDC2080 @@ -17974,10 +19954,14 @@ Sensor_Magnetic:LIS3MDL Sensor_Magnetic:MA730 Sensor_Magnetic:MMC5633NJL Sensor_Magnetic:MMC5883MA +Sensor_Magnetic:MT6701CT +Sensor_Magnetic:MT6701QT +Sensor_Magnetic:MT6816CT Sensor_Magnetic:SM351LT Sensor_Magnetic:SM353LT Sensor_Magnetic:Si7210-B-xx-IM2 Sensor_Magnetic:Si7210-B-xx-IV +Sensor_Magnetic:TLE5012B Sensor_Magnetic:TLV493D Sensor_Magnetic:TMAG5110A2xxDBV Sensor_Magnetic:TMAG5110A4xxDBV @@ -17999,6 +19983,10 @@ Sensor_Motion:BMI160 Sensor_Motion:BNO055 Sensor_Motion:ICM-20602 Sensor_Motion:ICM-20948 +Sensor_Motion:IIM-42652 +Sensor_Motion:IIS3DWB +Sensor_Motion:IPS2200 +Sensor_Motion:ISM330DHCX Sensor_Motion:KX022-1020 Sensor_Motion:KX122-1042 Sensor_Motion:KX222-1054 @@ -18021,11 +20009,13 @@ Sensor_Motion:MPU-6000 Sensor_Motion:MPU-6050 Sensor_Motion:MPU-9150 Sensor_Motion:MPU-9250 +Sensor_Motion:SC7A20 Sensor_Optical:A1050 Sensor_Optical:A1060 Sensor_Optical:A9013 Sensor_Optical:A9050 Sensor_Optical:A9060 +Sensor_Optical:APDS-9251-001 Sensor_Optical:APDS-9301 Sensor_Optical:APDS-9306 Sensor_Optical:APDS-9306-065 @@ -18086,16 +20076,19 @@ Sensor_Optical:SFH2440 Sensor_Optical:SFH2701 Sensor_Optical:SFH300 Sensor_Optical:SFH309 +Sensor_Optical:SFH320 Sensor_Optical:SFH3201 Sensor_Optical:TEPT4400 Sensor_Optical:TSL2550D Sensor_Optical:TSL2550T +Sensor_Optical:TSL25911FN Sensor_Optical:VT93xx Sensor_Pressure:40PC015G Sensor_Pressure:40PC100G Sensor_Pressure:40PC150G Sensor_Pressure:40PC250G Sensor_Pressure:BMP280 +Sensor_Pressure:LPS22DF Sensor_Pressure:LPS22HB Sensor_Pressure:LPS22HH Sensor_Pressure:LPS25HB @@ -18109,12 +20102,17 @@ Sensor_Pressure:MS5525DSO Sensor_Pressure:MS5607-02BA Sensor_Pressure:MS5611-01BA Sensor_Pressure:MS5837-xxBA +Sensor_Pressure:WSEN-PADS_2511020213301 Sensor_Pressure:XGZP6897D Sensor_Pressure:XGZP6899D +Sensor_Proximity:AD7150BRMZ +Sensor_Proximity:AD7151BRMZ +Sensor_Proximity:APDS-9160-003 Sensor_Proximity:BPR-105 Sensor_Proximity:BPR-105F Sensor_Proximity:BPR-205 Sensor_Proximity:CNY70 +Sensor_Proximity:GP2S700HCP Sensor_Proximity:ITR1201SR10AR Sensor_Proximity:ITR8307 Sensor_Proximity:ITR8307-F43 @@ -18130,6 +20128,7 @@ Sensor_Proximity:LG206D Sensor_Proximity:LG206L Sensor_Proximity:QRE1113 Sensor_Proximity:QRE1113GR +Sensor_Proximity:RPR-0720 Sensor_Proximity:SFH900 Sensor_Proximity:SFH9201 Sensor_Proximity:SFH9202 @@ -18177,9 +20176,14 @@ Sensor_Temperature:LM35-LP Sensor_Temperature:LM35-NEB Sensor_Temperature:LM73 Sensor_Temperature:LM73-1 +Sensor_Temperature:LM74CIM +Sensor_Temperature:LM74CITP Sensor_Temperature:LM75B Sensor_Temperature:LM75C Sensor_Temperature:LM92CIM +Sensor_Temperature:LM94021 +Sensor_Temperature:LMT01DQX +Sensor_Temperature:LMT01LPG Sensor_Temperature:LMT84DCK Sensor_Temperature:LMT85DCK Sensor_Temperature:LMT86DCK @@ -18236,10 +20240,13 @@ Sensor_Temperature:TMP102xxDRL Sensor_Temperature:TMP1075D Sensor_Temperature:TMP1075DGK Sensor_Temperature:TMP1075DSG +Sensor_Temperature:TMP110D Sensor_Temperature:TMP112xxDRL +Sensor_Temperature:TMP114 Sensor_Temperature:TMP116xxDRV Sensor_Temperature:TMP117xxDRV Sensor_Temperature:TMP117xxYBG +Sensor_Temperature:TMP119AIYBGR Sensor_Temperature:TMP20AIDCK Sensor_Temperature:TMP20AIDRL Sensor_Temperature:TMP36xS @@ -18305,6 +20312,7 @@ Simulation_SPICE:PMOS Simulation_SPICE:PMOS_Substrate Simulation_SPICE:PNP Simulation_SPICE:PNP_Substrate +Simulation_SPICE:SWITCH Simulation_SPICE:TLINE Simulation_SPICE:VAM Simulation_SPICE:VDC @@ -18316,6 +20324,8 @@ Simulation_SPICE:VSFFM Simulation_SPICE:VSIN Simulation_SPICE:VTRNOISE Simulation_SPICE:VTRRANDOM +Switch:CK_KMS2xxG +Switch:CK_KMS2xxGP Switch:SW_Coded Switch:SW_Coded_SH-7010 Switch:SW_Coded_SH-7030 @@ -18362,21 +20372,36 @@ Switch:SW_Push_Open Switch:SW_Push_Open_Dual Switch:SW_Push_Open_Dual_x2 Switch:SW_Push_SPDT +Switch:SW_Push_Shielded Switch:SW_Reed Switch:SW_Reed_Opener Switch:SW_Reed_SPDT -Switch:SW_Rotary12 -Switch:SW_Rotary2x6 -Switch:SW_Rotary3x4 -Switch:SW_Rotary4x3 +Switch:SW_Rotary_1x12 +Switch:SW_Rotary_1x3_MP +Switch:SW_Rotary_1x4_MP +Switch:SW_Rotary_1x5_MP +Switch:SW_Rotary_1x6_MP +Switch:SW_Rotary_1x7_MP +Switch:SW_Rotary_1x8_MP +Switch:SW_Rotary_1x9_MP +Switch:SW_Rotary_2x6 +Switch:SW_Rotary_3x4 +Switch:SW_Rotary_4x3 Switch:SW_SP3T +Switch:SW_SP3T_NR01103 +Switch:SW_SP4T_NR01104 +Switch:SW_SP5T_NR01105 Switch:SW_SPDT +Switch:SW_SPDT_312 Switch:SW_SPDT_321 Switch:SW_SPDT_MSM +Switch:SW_SPDT_XKB_DMx-xxxx-1 Switch:SW_SPST Switch:SW_SPST_LED Switch:SW_SPST_Lamp Switch:SW_SPST_Temperature +Switch:SW_Slide_DPDT +Switch:SW_Wuerth_450301014042 Timer:8253 Timer:8254 Timer:8284 @@ -18388,6 +20413,7 @@ Timer:AD9515 Timer:CD4541BE Timer:CD4541BM Timer:CD4541BPW +Timer:DS1023S Timer:ICM7209 Timer:ICM7555xB Timer:ICM7555xP @@ -18495,6 +20521,7 @@ Timer_RTC:PCF8523T Timer_RTC:PCF8523TK Timer_RTC:PCF8523TS Timer_RTC:PCF85263AT +Timer_RTC:PCF85263ATL Timer_RTC:PCF85263ATT Timer_RTC:PCF85263ATT1 Timer_RTC:PCF85363ATT @@ -18502,7 +20529,9 @@ Timer_RTC:PCF85363ATT1 Timer_RTC:PCF8563T Timer_RTC:PCF8563TS Timer_RTC:RV-1805-C3 +Timer_RTC:RV-3028-C7 Timer_RTC:RV-8523-C3 +Timer_RTC:RX8901CE Transformer:0433BM15A0001 Transformer:0868BM15C0001 Transformer:0896BM15A0001 @@ -18544,12 +20573,14 @@ Transformer:ADTT1-6 Transformer:ADTT1.5-1 Transformer:ADTT3-2 Transformer:ADTT4-1 +Transformer:B0322J5050AHF Transformer:CST1 Transformer:CST1_Split Transformer:CST2 Transformer:CST2010 Transformer:CST2010_Split Transformer:CST2_Split +Transformer:ED8_4 Transformer:ETC1-1-13 Transformer:LL1587 Transformer:P0544NL @@ -18576,6 +20607,7 @@ Transformer:PA3493NL Transformer:PE-68386NL Transformer:PT61017PEL Transformer:PT61020EL +Transformer:TC1-1-13M+ Transformer:TEZ0.5-D-1 Transformer:TEZ0.5-D-2 Transformer:TEZ1.5-D-1 @@ -18609,10 +20641,14 @@ Transformer:TRANSF5 Transformer:TRANSF6 Transformer:TRANSF7 Transformer:TRANSF8 +Transformer:Triad_VPP16-310 Transformer:Wuerth_749013011A Transformer:Wuerth_750315371 Transformer:Wuerth_750343373 +Transformer:Wuerth_760871131 Transformer:Wurth_750319177 +Transformer:ZMCT103C +Transformer:ZMPT101K Transistor_Array:A2982 Transistor_Array:MC1413BD Transistor_Array:MC1413BP @@ -18622,6 +20658,8 @@ Transistor_Array:NCV1413B Transistor_Array:SN75468 Transistor_Array:SN75469 Transistor_Array:TBD62783A +Transistor_Array:TBD62785AFWG +Transistor_Array:TBD62785APG Transistor_Array:ULN2002 Transistor_Array:ULN2002A Transistor_Array:ULN2003 @@ -18659,6 +20697,7 @@ Transistor_BJT:BC140 Transistor_BJT:BC141 Transistor_BJT:BC160 Transistor_BJT:BC161 +Transistor_BJT:BC212 Transistor_BJT:BC237 Transistor_BJT:BC240 Transistor_BJT:BC307 @@ -18785,6 +20824,7 @@ Transistor_BJT:BFR92 Transistor_BJT:BFT92 Transistor_BJT:BUT11 Transistor_BJT:BUT11A +Transistor_BJT:DMMT5401 Transistor_BJT:DTA113T Transistor_BJT:DTA113Z Transistor_BJT:DTA114E @@ -18892,22 +20932,47 @@ Transistor_BJT:MJE13003 Transistor_BJT:MJE13005G Transistor_BJT:MJE13007G Transistor_BJT:MJE13009G +Transistor_BJT:MMBT2222A Transistor_BJT:MMBT3904 Transistor_BJT:MMBT3906 Transistor_BJT:MMBT5550L Transistor_BJT:MMBT5551L +Transistor_BJT:MMBTA06 Transistor_BJT:MMBTA42 +Transistor_BJT:MMBTA44 +Transistor_BJT:MMBTA56 Transistor_BJT:MMBTA92 +Transistor_BJT:MMBTA94 Transistor_BJT:MMDT2222A Transistor_BJT:MMDT3904 Transistor_BJT:MMDT3906 Transistor_BJT:MMDT3946 Transistor_BJT:MMDT5401 Transistor_BJT:MMDT5551 +Transistor_BJT:MMDTA06 Transistor_BJT:MPSA42 Transistor_BJT:MPSA92 +Transistor_BJT:MUN5111DW1 +Transistor_BJT:MUN5112DW1 +Transistor_BJT:MUN5113DW1 +Transistor_BJT:MUN5114DW1 Transistor_BJT:MUN5211DW1 +Transistor_BJT:MUN5212DW1 +Transistor_BJT:MUN5213DW1 +Transistor_BJT:MUN5214DW1 +Transistor_BJT:MUN5311DW1 +Transistor_BJT:MUN5312DW1 +Transistor_BJT:MUN5313DW1 +Transistor_BJT:MUN5314DW1 +Transistor_BJT:MUN5330DW1 +Transistor_BJT:MUN5331DW1 +Transistor_BJT:MUN5332DW1 +Transistor_BJT:MUN5333DW1 +Transistor_BJT:MUN5334DW1 +Transistor_BJT:MUN5335DW1 +Transistor_BJT:MUN5336DW1 Transistor_BJT:PBSS301PZ +Transistor_BJT:PMBT2222A Transistor_BJT:PMBT2222AYS Transistor_BJT:PMBT3904YS Transistor_BJT:PMBT3906YS @@ -18920,8 +20985,62 @@ Transistor_BJT:PZT3904 Transistor_BJT:PZT3906 Transistor_BJT:PZTA42 Transistor_BJT:PZTA92 +Transistor_BJT:Q_Dual_NPN_C2C1E1E2 +Transistor_BJT:Q_Dual_NPN_NPN_B1E2B2C2E1C1 +Transistor_BJT:Q_Dual_NPN_NPN_BRT_E1B1C2E2B2C1 +Transistor_BJT:Q_Dual_NPN_NPN_BRT_No_R2_C1B2E2C2B1E1 +Transistor_BJT:Q_Dual_NPN_NPN_BRT_No_R2_E1B1C2E2B2C1 +Transistor_BJT:Q_Dual_NPN_NPN_C1E1C2E2B2B1 +Transistor_BJT:Q_Dual_NPN_NPN_E1B1C2E2B2C1 +Transistor_BJT:Q_Dual_NPN_PNP_B1E2B2C2E1C1 +Transistor_BJT:Q_Dual_NPN_PNP_BRT_E1B1C2E2B2C1 +Transistor_BJT:Q_Dual_NPN_PNP_E1B1C2E2B2C1 +Transistor_BJT:Q_Dual_PNP_C2C1E1E2 +Transistor_BJT:Q_Dual_PNP_NPN_BRT_E1B1C2E2B2C1 +Transistor_BJT:Q_Dual_PNP_PNP_BRT_E1B1C2E2B2C1 +Transistor_BJT:Q_Dual_PNP_PNP_C1B1B2C2E2E1 +Transistor_BJT:Q_Dual_PNP_PNP_C1E1C2E2B2B1 +Transistor_BJT:Q_Dual_PNP_PNP_E1B1C2E2B2C1 +Transistor_BJT:Q_NPN_BCE +Transistor_BJT:Q_NPN_BCEC +Transistor_BJT:Q_NPN_BEC +Transistor_BJT:Q_NPN_BRT_BEC +Transistor_BJT:Q_NPN_BRT_ECB +Transistor_BJT:Q_NPN_CBE +Transistor_BJT:Q_NPN_CEB +Transistor_BJT:Q_NPN_Darlington_BCE +Transistor_BJT:Q_NPN_Darlington_BCEC +Transistor_BJT:Q_NPN_Darlington_BEC +Transistor_BJT:Q_NPN_Darlington_CBE +Transistor_BJT:Q_NPN_Darlington_CEB +Transistor_BJT:Q_NPN_Darlington_EBC +Transistor_BJT:Q_NPN_Darlington_ECB +Transistor_BJT:Q_NPN_Darlington_ECBC +Transistor_BJT:Q_NPN_EBC +Transistor_BJT:Q_NPN_ECB +Transistor_BJT:Q_NPN_ECBC +Transistor_BJT:Q_PNP_BCE +Transistor_BJT:Q_PNP_BCEC +Transistor_BJT:Q_PNP_BEC +Transistor_BJT:Q_PNP_BRT_BEC +Transistor_BJT:Q_PNP_BRT_ECB +Transistor_BJT:Q_PNP_CBE +Transistor_BJT:Q_PNP_CEB +Transistor_BJT:Q_PNP_Darlington_BCE +Transistor_BJT:Q_PNP_Darlington_BCEC +Transistor_BJT:Q_PNP_Darlington_BEC +Transistor_BJT:Q_PNP_Darlington_CBE +Transistor_BJT:Q_PNP_Darlington_CEB +Transistor_BJT:Q_PNP_Darlington_EBC +Transistor_BJT:Q_PNP_Darlington_ECB +Transistor_BJT:Q_PNP_Darlington_ECBC +Transistor_BJT:Q_PNP_EBC +Transistor_BJT:Q_PNP_ECB +Transistor_BJT:Q_PNP_ECBC Transistor_BJT:S8050 Transistor_BJT:S8550 +Transistor_BJT:SS8050 +Transistor_BJT:SS8550 Transistor_BJT:SSM2210 Transistor_BJT:SSM2220 Transistor_BJT:TIP120 @@ -18952,6 +21071,8 @@ Transistor_FET:2N7002K Transistor_FET:3SK263 Transistor_FET:AO3400A Transistor_FET:AO3401A +Transistor_FET:AO4842 +Transistor_FET:AO4892 Transistor_FET:AON6411 Transistor_FET:BF244A Transistor_FET:BF244B @@ -18962,6 +21083,7 @@ Transistor_FET:BF245C Transistor_FET:BF545A Transistor_FET:BF545B Transistor_FET:BF545C +Transistor_FET:BF994S Transistor_FET:BS107 Transistor_FET:BS108 Transistor_FET:BS170 @@ -19014,6 +21136,7 @@ Transistor_FET:BSC265N10LSFG Transistor_FET:BSC340N08NS3G Transistor_FET:BSC440N10NS3G Transistor_FET:BSD235C +Transistor_FET:BSD840N Transistor_FET:BSF030NE2LQ Transistor_FET:BSF035NE2LQ Transistor_FET:BSF450NE7NH3 @@ -19046,6 +21169,7 @@ Transistor_FET:BUK7M67-60EX Transistor_FET:BUK7M6R3-40EX Transistor_FET:BUK7M8R0-40EX Transistor_FET:BUK7M9R9-60EX +Transistor_FET:BUK9832-55A Transistor_FET:BUK9M10-30EX Transistor_FET:BUK9M11-40EX Transistor_FET:BUK9M12-60EX @@ -19092,10 +21216,12 @@ Transistor_FET:C3M0120100J Transistor_FET:C3M0120100K Transistor_FET:C3M0280090D Transistor_FET:C3M0280090J +Transistor_FET:CSD13380F3 Transistor_FET:CSD16301Q2 Transistor_FET:CSD16321Q5 Transistor_FET:CSD16322Q5 Transistor_FET:CSD16325Q5 +Transistor_FET:CSD16327Q3 Transistor_FET:CSD16342Q5A Transistor_FET:CSD16401Q5 Transistor_FET:CSD16403Q5A @@ -19136,9 +21262,11 @@ Transistor_FET:CSD17559Q5 Transistor_FET:CSD17570Q5B Transistor_FET:CSD17573Q5B Transistor_FET:CSD17576Q5B +Transistor_FET:CSD17577Q3A Transistor_FET:CSD17577Q5A Transistor_FET:CSD17578Q5A Transistor_FET:CSD17579Q5A +Transistor_FET:CSD17581Q3A Transistor_FET:CSD18501Q5A Transistor_FET:CSD18502Q5B Transistor_FET:CSD18503Q5A @@ -19151,6 +21279,7 @@ Transistor_FET:CSD18533Q5A Transistor_FET:CSD18534Q5A Transistor_FET:CSD18537NQ5A Transistor_FET:CSD18540Q5B +Transistor_FET:CSD18543Q3A Transistor_FET:CSD18563Q5A Transistor_FET:CSD19502Q5B Transistor_FET:CSD19531Q5A @@ -19159,6 +21288,8 @@ Transistor_FET:CSD19533Q5A Transistor_FET:CSD19534Q5A Transistor_FET:CSD19537Q3 Transistor_FET:CSD25302Q2 +Transistor_FET:CSD25402Q3A +Transistor_FET:CSD25480F3 Transistor_FET:DMC2053UVT Transistor_FET:DMC3071LVT Transistor_FET:DMG1012T @@ -19173,6 +21304,7 @@ Transistor_FET:DMG9926UDM Transistor_FET:DMN10H220L Transistor_FET:DMN10H700S Transistor_FET:DMN13H750S +Transistor_FET:DMN2040U Transistor_FET:DMN2041L Transistor_FET:DMN2050L Transistor_FET:DMN2056U @@ -19181,6 +21313,7 @@ Transistor_FET:DMN2075U Transistor_FET:DMN2230U Transistor_FET:DMN24H11DS Transistor_FET:DMN24H3D5L +Transistor_FET:DMN3008SFG Transistor_FET:DMN3033LDM Transistor_FET:DMN3042L Transistor_FET:DMN3051L @@ -19193,9 +21326,18 @@ Transistor_FET:DMN3404L Transistor_FET:DMN6075S Transistor_FET:DMN60H080DS Transistor_FET:DMN6140L +Transistor_FET:DMN61D8LQ Transistor_FET:DMN67D7L Transistor_FET:DMN67D8L Transistor_FET:DMP3013SFV +Transistor_FET:DMP6050SSD +Transistor_FET:DMT6008LFG +Transistor_FET:EPC2035 +Transistor_FET:EPC2036 +Transistor_FET:EPC2037 +Transistor_FET:EPC2038 +Transistor_FET:EPC2203 +Transistor_FET:EPC2219 Transistor_FET:FDC2512 Transistor_FET:FDC6330L Transistor_FET:FDC86244 @@ -19239,7 +21381,10 @@ Transistor_FET:FDS9435A Transistor_FET:FDS9926A Transistor_FET:FDS9934C Transistor_FET:FQP27P06 +Transistor_FET:GS66502B +Transistor_FET:GS66504B Transistor_FET:GS66508B +Transistor_FET:IF3602 Transistor_FET:IGLD60R070D1 Transistor_FET:IGLD60R190D1 Transistor_FET:IGO60R070D1 @@ -19375,6 +21520,9 @@ Transistor_FET:IRLML9301 Transistor_FET:IRLZ24 Transistor_FET:IRLZ34N Transistor_FET:IRLZ44N +Transistor_FET:JFE150DBV +Transistor_FET:JFE150DCK +Transistor_FET:JFE2140D Transistor_FET:MMBF170 Transistor_FET:MMBF4391 Transistor_FET:MMBF4392 @@ -19391,6 +21539,54 @@ Transistor_FET:PMN48XP Transistor_FET:PSMN5R2-60YL Transistor_FET:QM6006D Transistor_FET:QM6015D +Transistor_FET:Q_Dual_NMOS_G1S2G2D2S1D1 +Transistor_FET:Q_Dual_NMOS_S1G1D2S2G2D1 +Transistor_FET:Q_Dual_NMOS_S1G1S2G2D2D1 +Transistor_FET:Q_Dual_NMOS_S1G1S2G2D2D2D1D1 +Transistor_FET:Q_Dual_PMOS_G1S2G2D2S1D1 +Transistor_FET:Q_Dual_PMOS_S1G1D2S2G2D1 +Transistor_FET:Q_Dual_PMOS_S1G1S2G2D2D2D1D1 +Transistor_FET:Q_NMOS_DGS +Transistor_FET:Q_NMOS_DSG +Transistor_FET:Q_NMOS_GDS +Transistor_FET:Q_NMOS_GDSD +Transistor_FET:Q_NMOS_GSD +Transistor_FET:Q_NMOS_SDGD +Transistor_FET:Q_NMOS_SGD +Transistor_FET:Q_PMOS_DGS +Transistor_FET:Q_PMOS_DSG +Transistor_FET:Q_PMOS_GDS +Transistor_FET:Q_PMOS_GDSD +Transistor_FET:Q_PMOS_GSD +Transistor_FET:Q_PMOS_SDG +Transistor_FET:Q_PMOS_SDGD +Transistor_FET:Q_PMOS_SGD +Transistor_FET:RQ6E080AJ +Transistor_FET:RS9N50D +Transistor_FET:RSQ030N08HZG +Transistor_FET:SCTL35N65G2V +Transistor_FET:SGT65R65AL +Transistor_FET:SQJQ100E +Transistor_FET:SQJQ100EL +Transistor_FET:SQJQ112E +Transistor_FET:SQJQ114EL +Transistor_FET:SQJQ116EL +Transistor_FET:SQJQ130EL +Transistor_FET:SQJQ140E +Transistor_FET:SQJQ142E +Transistor_FET:SQJQ144AE +Transistor_FET:SQJQ146E +Transistor_FET:SQJQ148E +Transistor_FET:SQJQ150E +Transistor_FET:SQJQ160E +Transistor_FET:SQJQ160EL +Transistor_FET:SQJQ184E +Transistor_FET:SQJQ186E +Transistor_FET:SQJQ402E +Transistor_FET:SQJQ404E +Transistor_FET:SQJQ410EL +Transistor_FET:SQJQ466E +Transistor_FET:SQJQ480E Transistor_FET:STB15N80K5 Transistor_FET:STB33N65M2 Transistor_FET:STB40N60M2 @@ -19404,6 +21600,7 @@ Transistor_FET:SUD50P04-08 Transistor_FET:SUD50P06-15 Transistor_FET:SUD50P08-25L Transistor_FET:SUD50P10-43L +Transistor_FET:Si1308EDL Transistor_FET:Si1442DH Transistor_FET:Si2319CDS Transistor_FET:Si2371EDS @@ -19417,10 +21614,13 @@ Transistor_FET:Si7450DP Transistor_FET:Si7617DN Transistor_FET:SiA449DJ Transistor_FET:SiA453EDJ +Transistor_FET:SiA462DJ +Transistor_FET:SiR696DP Transistor_FET:SiS415DNT Transistor_FET:SiS443DN Transistor_FET:SiS454DN Transistor_FET:SiSS27DN +Transistor_FET:T2N7002AK Transistor_FET:TP0610L Transistor_FET:TP0610T Transistor_FET:TSM2301ACX @@ -19452,6 +21652,12 @@ Transistor_FET:ZXMP4A16G Transistor_FET_Other:DN2540N3-G Transistor_FET_Other:DN2540N5-G Transistor_FET_Other:DN2540N8-G +Transistor_FET_Other:Q_NMOS_Depletion_DGS +Transistor_FET_Other:Q_NMOS_Depletion_DSG +Transistor_FET_Other:Q_NMOS_Depletion_GDS +Transistor_FET_Other:Q_NMOS_Depletion_GSD +Transistor_FET_Other:Q_NMOS_Depletion_SDG +Transistor_FET_Other:Q_NMOS_Depletion_SGD Transistor_IGBT:IRG4PF50W Transistor_IGBT:STGP7NC60HD Transistor_Power_Module:A2C25S12M3 @@ -19514,6 +21720,13 @@ Triac_Thyristor:BTB16-800BW Triac_Thyristor:BTB16-800C Triac_Thyristor:BTB16-800CW Triac_Thyristor:BTB16-800SW +Triac_Thyristor:CT401T +Triac_Thyristor:Generic_Triac_A1A2G +Triac_Thyristor:Generic_Triac_A1GA2 +Triac_Thyristor:Generic_Triac_A2A1G +Triac_Thyristor:Generic_Triac_A2GA1 +Triac_Thyristor:Generic_Triac_GA1A2 +Triac_Thyristor:Generic_Triac_GA2A1 Triac_Thyristor:TIC106 Triac_Thyristor:TIC116 Triac_Thyristor:TIC126 diff --git a/rector.php b/rector.php index 40eee9f76..936b447ee 100644 --- a/rector.php +++ b/rector.php @@ -7,6 +7,7 @@ use Rector\Config\RectorConfig; use Rector\Doctrine\Set\DoctrineSetList; use Rector\PHPUnit\CodeQuality\Rector\Class_\PreferPHPUnitThisCallRector; +use Rector\PHPUnit\CodeQuality\Rector\MethodCall\AssertEmptyNullableObjectToAssertInstanceofRector; use Rector\PHPUnit\Set\PHPUnitSetList; use Rector\Set\ValueObject\LevelSetList; use Rector\Set\ValueObject\SetList; @@ -16,6 +17,61 @@ use Rector\Symfony\Set\SymfonySetList; use Rector\TypeDeclaration\Rector\StmtsAwareInterface\DeclareStrictTypesRector; +return RectorConfig::configure() + ->withComposerBased(phpunit: true) + + ->withSymfonyContainerPhp(__DIR__ . '/tests/symfony-container.php') + ->withSymfonyContainerXml(__DIR__ . '/var/cache/dev/App_KernelDevDebugContainer.xml') + + ->withImportNames(importShortClasses: false) + ->withPaths([ + __DIR__ . '/config', + __DIR__ . '/public', + __DIR__ . '/src', + __DIR__ . '/tests', + ]) + + ->withSets([ + PHPUnitSetList::ANNOTATIONS_TO_ATTRIBUTES, + PHPUnitSetList::PHPUNIT_90, + PHPUnitSetList::PHPUNIT_110, + PHPUnitSetList::PHPUNIT_CODE_QUALITY, + + + ]) + + ->withRules([ + DeclareStrictTypesRector::class + ]) + + ->withSkip([ + //Leave our AssertNull tests alone + AssertEmptyNullableObjectToAssertInstanceofRector::class, + + + CountArrayToEmptyArrayComparisonRector::class, + //Leave our !== null checks alone + FlipTypeControlToUseExclusiveTypeRector::class, + //Leave our PartList TableAction alone + ActionSuffixRemoverRector::class, + //We declare event listeners via attributes, therefore no need to migrate them to subscribers + EventListenerToEventSubscriberRector::class, + PreferPHPUnitThisCallRector::class, + //Do not replace 'GET' with class constant, + LiteralGetToRequestClassConstantRector::class, + ]) + + //Do not apply rules to Symfony own files + ->withSkip([ + __DIR__ . '/public/index.php', + __DIR__ . '/src/Kernel.php', + __DIR__ . '/config/preload.php', + __DIR__ . '/config/bundles.php', + ]) + + ; + +/* return static function (RectorConfig $rectorConfig): void { $rectorConfig->symfonyContainerXml(__DIR__ . '/var/cache/dev/App_KernelDevDebugContainer.xml'); $rectorConfig->symfonyContainerPhp(__DIR__ . '/tests/symfony-container.php'); @@ -79,3 +135,4 @@ __DIR__ . '/config/bundles.php', ]); }; +*/ diff --git a/src/ApiPlatform/AddDocumentedAPIPropertiesJSONSchemaFactory.php b/src/ApiPlatform/AddDocumentedAPIPropertiesJSONSchemaFactory.php deleted file mode 100644 index db629a4a1..000000000 --- a/src/ApiPlatform/AddDocumentedAPIPropertiesJSONSchemaFactory.php +++ /dev/null @@ -1,116 +0,0 @@ -. - */ - -declare(strict_types=1); - - -namespace App\ApiPlatform; - -use ApiPlatform\JsonSchema\Schema; -use ApiPlatform\JsonSchema\SchemaFactoryInterface; -use ApiPlatform\Metadata\Operation; -use Symfony\Component\DependencyInjection\Attribute\AsDecorator; - -/** - * This decorator adds the properties given by DocumentedAPIProperty attributes on the classes to the schema. - */ -#[AsDecorator('api_platform.json_schema.schema_factory')] -class AddDocumentedAPIPropertiesJSONSchemaFactory implements SchemaFactoryInterface -{ - - public function __construct(private readonly SchemaFactoryInterface $decorated) - { - } - - public function buildSchema( - string $className, - string $format = 'json', - string $type = Schema::TYPE_OUTPUT, - Operation $operation = null, - Schema $schema = null, - array $serializerContext = null, - bool $forceCollection = false - ): Schema { - - - $schema = $this->decorated->buildSchema($className, $format, $type, $operation, $schema, $serializerContext, $forceCollection); - - //Check if there is are DocumentedAPIProperty attributes on the class - $reflectionClass = new \ReflectionClass($className); - $attributes = $reflectionClass->getAttributes(DocumentedAPIProperty::class); - foreach ($attributes as $attribute) { - /** @var DocumentedAPIProperty $api_property */ - $api_property = $attribute->newInstance(); - $this->addPropertyToSchema($schema, $api_property->schemaName, $api_property->property, - $api_property, $serializerContext ?? [], $format); - } - - return $schema; - } - - private function addPropertyToSchema(Schema $schema, string $definitionName, string $normalizedPropertyName, DocumentedAPIProperty $propertyMetadata, array $serializerContext, string $format): void - { - $version = $schema->getVersion(); - $swagger = Schema::VERSION_SWAGGER === $version; - - $propertySchema = []; - - if (false === $propertyMetadata->writeable) { - $propertySchema['readOnly'] = true; - } - if (!$swagger && false === $propertyMetadata->readable) { - $propertySchema['writeOnly'] = true; - } - if (null !== $description = $propertyMetadata->description) { - $propertySchema['description'] = $description; - } - - $deprecationReason = $propertyMetadata->deprecationReason; - - // see https://github.com/json-schema-org/json-schema-spec/pull/737 - if (!$swagger && null !== $deprecationReason) { - $propertySchema['deprecated'] = true; - } - - if (!empty($default = $propertyMetadata->default)) { - if ($default instanceof \BackedEnum) { - $default = $default->value; - } - $propertySchema['default'] = $default; - } - - if (!empty($example = $propertyMetadata->example)) { - $propertySchema['example'] = $example; - } - - if (!isset($propertySchema['example']) && isset($propertySchema['default'])) { - $propertySchema['example'] = $propertySchema['default']; - } - - $propertySchema['type'] = $propertyMetadata->type; - $propertySchema['nullable'] = $propertyMetadata->nullable; - - $propertySchema = new \ArrayObject($propertySchema); - - $schema->getDefinitions()[$definitionName]['properties'][$normalizedPropertyName] = $propertySchema; - } - - -} \ No newline at end of file diff --git a/src/ApiPlatform/DocumentedAPIProperty.php b/src/ApiPlatform/DocumentedAPIProperties/DocumentedAPIProperty.php similarity index 59% rename from src/ApiPlatform/DocumentedAPIProperty.php rename to src/ApiPlatform/DocumentedAPIProperties/DocumentedAPIProperty.php index c4c0a3377..57d275be4 100644 --- a/src/ApiPlatform/DocumentedAPIProperty.php +++ b/src/ApiPlatform/DocumentedAPIProperties/DocumentedAPIProperty.php @@ -21,7 +21,9 @@ declare(strict_types=1); -namespace App\ApiPlatform; +namespace App\ApiPlatform\DocumentedAPIProperties; + +use ApiPlatform\Metadata\ApiProperty; /** * When this attribute is applied to a class, an property will be added to the API documentation using the given parameters. @@ -64,4 +66,55 @@ public function __construct( ) { } + + public function toAPIProperty(bool $use_swagger = false): ApiProperty + { + $openApiContext = []; + + if (false === $this->writeable) { + $openApiContext['readOnly'] = true; + } + if (!$use_swagger && false === $this->readable) { + $openApiContext['writeOnly'] = true; + } + if (null !== $description = $this->description) { + $openApiContext['description'] = $description; + } + + $deprecationReason = $this->deprecationReason; + + // see https://github.com/json-schema-org/json-schema-spec/pull/737 + if (!$use_swagger && null !== $deprecationReason) { + $openApiContext['deprecated'] = true; + } + + if (!empty($default = $this->default)) { + if ($default instanceof \BackedEnum) { + $default = $default->value; + } + $openApiContext['default'] = $default; + } + + if (!empty($example = $this->example)) { + $openApiContext['example'] = $example; + } + + if (!isset($openApiContext['example']) && isset($openApiContext['default'])) { + $openApiContext['example'] = $openApiContext['default']; + } + + $openApiContext['type'] = $this->type; + $openApiContext['nullable'] = $this->nullable; + + + + return new ApiProperty( + description: $this->description, + readable: $this->readable, + writable: $this->writeable, + openapiContext: $openApiContext, + types: $this->type, + property: $this->property + ); + } } \ No newline at end of file diff --git a/src/ApiPlatform/DocumentedAPIProperties/PropertyMetadataFactory.php b/src/ApiPlatform/DocumentedAPIProperties/PropertyMetadataFactory.php new file mode 100644 index 000000000..2ffb91791 --- /dev/null +++ b/src/ApiPlatform/DocumentedAPIProperties/PropertyMetadataFactory.php @@ -0,0 +1,73 @@ +. + */ + +declare(strict_types=1); + + +namespace App\ApiPlatform\DocumentedAPIProperties; + +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; +use ReflectionClass; +use Symfony\Component\DependencyInjection\Attribute\AsDecorator; + +/** + * This decorator adds the virtual properties defined by the DocumentedAPIProperty attribute to the property metadata + * which then get picked up by the openapi schema generator + */ +#[AsDecorator('api_platform.metadata.property.metadata_factory')] +class PropertyMetadataFactory implements PropertyMetadataFactoryInterface +{ + public function __construct(private PropertyMetadataFactoryInterface $decorated) + { + } + + public function create(string $resourceClass, string $property, array $options = []): ApiProperty + { + $metadata = $this->decorated->create($resourceClass, $property, $options); + + //Only become active in the context of the openapi schema generation + if (!isset($options['schema_type'])) { + return $metadata; + } + + if (!class_exists($resourceClass)) { + return $metadata; + } + + $refClass = new ReflectionClass($resourceClass); + $attributes = $refClass->getAttributes(DocumentedAPIProperty::class); + + //Look for the DocumentedAPIProperty attribute with the given property name + foreach ($attributes as $attribute) { + /** @var DocumentedAPIProperty $api_property */ + $api_property = $attribute->newInstance(); + //If attribute not matches the property name, skip it + if ($api_property->property !== $property) { + continue; + } + + //Return the virtual property + return $api_property->toAPIProperty(); + } + + return $metadata; + } +} diff --git a/src/ApiPlatform/DocumentedAPIProperties/PropertyNameCollectionFactory.php b/src/ApiPlatform/DocumentedAPIProperties/PropertyNameCollectionFactory.php new file mode 100644 index 000000000..3157cbf38 --- /dev/null +++ b/src/ApiPlatform/DocumentedAPIProperties/PropertyNameCollectionFactory.php @@ -0,0 +1,68 @@ +. + */ + +declare(strict_types=1); + + +namespace App\ApiPlatform\DocumentedAPIProperties; + +use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; +use ApiPlatform\Metadata\Property\PropertyNameCollection; +use ReflectionClass; +use Symfony\Component\DependencyInjection\Attribute\AsDecorator; + +/** + * This decorator adds the virtual property names defined by the DocumentedAPIProperty attribute to the property name collection + * which then get picked up by the openapi schema generator + */ +#[AsDecorator('api_platform.metadata.property.name_collection_factory')] +class PropertyNameCollectionFactory implements PropertyNameCollectionFactoryInterface +{ + public function __construct(private readonly PropertyNameCollectionFactoryInterface $decorated) + { + } + + public function create(string $resourceClass, array $options = []): PropertyNameCollection + { + // Get the default properties from the decorated service + $propertyNames = $this->decorated->create($resourceClass, $options); + + //Only become active in the context of the openapi schema generation + if (!isset($options['schema_type'])) { + return $propertyNames; + } + + if (!class_exists($resourceClass)) { + return $propertyNames; + } + + $properties = iterator_to_array($propertyNames); + + $refClass = new ReflectionClass($resourceClass); + + foreach ($refClass->getAttributes(DocumentedAPIProperty::class) as $attribute) { + /** @var DocumentedAPIProperty $instance */ + $instance = $attribute->newInstance(); + $properties[] = $instance->property; + } + + return new PropertyNameCollection($properties); + } +} \ No newline at end of file diff --git a/src/ApiPlatform/Filter/EntityFilter.php b/src/ApiPlatform/Filter/EntityFilter.php index 50c1404ff..85bc38337 100644 --- a/src/ApiPlatform/Filter/EntityFilter.php +++ b/src/ApiPlatform/Filter/EntityFilter.php @@ -37,7 +37,7 @@ class EntityFilter extends AbstractFilter public function __construct( ManagerRegistry $managerRegistry, private readonly EntityFilterHelper $filter_helper, - LoggerInterface $logger = null, + ?LoggerInterface $logger = null, ?array $properties = null, ?NameConverterInterface $nameConverter = null ) { @@ -50,7 +50,7 @@ protected function filterProperty( QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, - Operation $operation = null, + ?Operation $operation = null, array $context = [] ): void { if ( diff --git a/src/ApiPlatform/Filter/EntityFilterHelper.php b/src/ApiPlatform/Filter/EntityFilterHelper.php index 42cc567f1..45e04fdef 100644 --- a/src/ApiPlatform/Filter/EntityFilterHelper.php +++ b/src/ApiPlatform/Filter/EntityFilterHelper.php @@ -92,12 +92,6 @@ public function getDescription(array $properties): array 'type' => Type::BUILTIN_TYPE_STRING, 'required' => false, 'description' => 'Filter using a comma seperated list of element IDs. Use + to include all direct children and ++ to include all children recursively.', - 'openapi' => [ - 'example' => '', - 'allowReserved' => false,// if true, query parameters will be not percent-encoded - 'allowEmptyValue' => true, - 'explode' => false, // to be true, the type must be Type::BUILTIN_TYPE_ARRAY, ?product=blue,green will be ?product=blue&product=green - ], ]; } return $description; diff --git a/src/ApiPlatform/Filter/LikeFilter.php b/src/ApiPlatform/Filter/LikeFilter.php index 08fc1b3a4..a8e96eb9b 100644 --- a/src/ApiPlatform/Filter/LikeFilter.php +++ b/src/ApiPlatform/Filter/LikeFilter.php @@ -38,7 +38,7 @@ protected function filterProperty( QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, - Operation $operation = null, + ?Operation $operation = null, array $context = [] ): void { // Otherwise filter is applied to order and page as well @@ -67,12 +67,6 @@ public function getDescription(string $resourceClass): array 'type' => Type::BUILTIN_TYPE_STRING, 'required' => false, 'description' => 'Filter using a LIKE SQL expression. Use % as wildcard for multiple characters and _ for single characters. For example, to search for all items containing foo, use foo. To search for all items starting with foo, use foo%. To search for all items ending with foo, use %foo', - 'openapi' => [ - 'example' => '', - 'allowReserved' => false,// if true, query parameters will be not percent-encoded - 'allowEmptyValue' => true, - 'explode' => false, // to be true, the type must be Type::BUILTIN_TYPE_ARRAY, ?product=blue,green will be ?product=blue&product=green - ], ]; } return $description; diff --git a/src/ApiPlatform/Filter/PartStoragelocationFilter.php b/src/ApiPlatform/Filter/PartStoragelocationFilter.php index 860fb320d..4d0ad2df7 100644 --- a/src/ApiPlatform/Filter/PartStoragelocationFilter.php +++ b/src/ApiPlatform/Filter/PartStoragelocationFilter.php @@ -38,7 +38,7 @@ class PartStoragelocationFilter extends AbstractFilter public function __construct( ManagerRegistry $managerRegistry, private readonly EntityFilterHelper $filter_helper, - LoggerInterface $logger = null, + ?LoggerInterface $logger = null, ?array $properties = null, ?NameConverterInterface $nameConverter = null ) { @@ -51,7 +51,7 @@ protected function filterProperty( QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, - Operation $operation = null, + ?Operation $operation = null, array $context = [] ): void { //Do not check for mapping here, as we are using a virtual property diff --git a/src/ApiPlatform/Filter/TagFilter.php b/src/ApiPlatform/Filter/TagFilter.php index b8be0657c..98648ee91 100644 --- a/src/ApiPlatform/Filter/TagFilter.php +++ b/src/ApiPlatform/Filter/TagFilter.php @@ -89,12 +89,6 @@ public function getDescription(string $resourceClass): array '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; diff --git a/src/ApiPlatform/NormalizePropertyNameCollectionFactory.php b/src/ApiPlatform/NormalizePropertyNameCollectionFactory.php new file mode 100644 index 000000000..c6a8220e9 --- /dev/null +++ b/src/ApiPlatform/NormalizePropertyNameCollectionFactory.php @@ -0,0 +1,77 @@ +. + */ + +declare(strict_types=1); + + +namespace App\ApiPlatform; + +use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; +use ApiPlatform\Metadata\Property\PropertyNameCollection; +use Symfony\Component\DependencyInjection\Attribute\AsDecorator; +use function Symfony\Component\String\u; + +/** + * This decorator removes all camelCase property names from the property name collection, if a snake_case version exists. + * This is a fix for https://github.com/Part-DB/Part-DB-server/issues/862, as the openapi schema generator wrongly collects + * both camelCase and snake_case property names, which leads to duplicate properties in the schema. + * This seems to come from the fact that the openapi schema generator uses no serializerContext, which seems then to collect + * the getters too... + */ +#[AsDecorator('api_platform.metadata.property.name_collection_factory')] +class NormalizePropertyNameCollectionFactory implements PropertyNameCollectionFactoryInterface +{ + public function __construct(private readonly PropertyNameCollectionFactoryInterface $decorated) + { + } + + public function create(string $resourceClass, array $options = []): PropertyNameCollection + { + // Get the default properties from the decorated service + $propertyNames = $this->decorated->create($resourceClass, $options); + + //Only become active in the context of the openapi schema generation + if (!isset($options['schema_type'])) { + return $propertyNames; + } + + //If we are not in the jsonapi generator (which sets no serializer groups), return the property names as is + if (isset($options['serializer_groups'])) { + return $propertyNames; + } + + //Remove all camelCase property names from the collection, if a snake_case version exists + $properties = iterator_to_array($propertyNames); + + foreach ($properties as $property) { + if (str_contains($property, '_')) { + $camelized = u($property)->camel()->toString(); + + //If the camelized version exists, remove it from the collection + $index = array_search($camelized, $properties, true); + if ($index !== false) { + unset($properties[$index]); + } + } + } + + return new PropertyNameCollection($properties); + } +} \ No newline at end of file diff --git a/src/Command/Attachments/CleanAttachmentsCommand.php b/src/Command/Attachments/CleanAttachmentsCommand.php index e9ffd286a..59bc99eeb 100644 --- a/src/Command/Attachments/CleanAttachmentsCommand.php +++ b/src/Command/Attachments/CleanAttachmentsCommand.php @@ -73,6 +73,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int //Ignore image cache folder $finder->exclude('cache'); + //Ignore automigration folder + $finder->exclude('.automigration-backup'); + $fs = new Filesystem(); $file_list = []; diff --git a/src/Command/Attachments/DownloadAttachmentsCommand.php b/src/Command/Attachments/DownloadAttachmentsCommand.php new file mode 100644 index 000000000..34deef0ed --- /dev/null +++ b/src/Command/Attachments/DownloadAttachmentsCommand.php @@ -0,0 +1,136 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Command\Attachments; + +use App\Entity\Attachments\Attachment; +use App\Entity\Attachments\AttachmentUpload; +use App\Exceptions\AttachmentDownloadException; +use App\Services\Attachments\AttachmentManager; +use App\Services\Attachments\AttachmentSubmitHandler; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +#[AsCommand('partdb:attachments:download', "Downloads all attachments which have only an external URL to the local filesystem.")] +class DownloadAttachmentsCommand extends Command +{ + public function __construct(private readonly AttachmentSubmitHandler $attachmentSubmitHandler, + private EntityManagerInterface $entityManager) + { + parent::__construct(); + } + + public function configure(): void + { + $this->setHelp('This command downloads all attachments, which only have an external URL, to the local filesystem, so that you have an offline copy of the attachments.'); + $this->addOption('--private', null, null, 'If set, the attachments will be downloaded to the private storage.'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $qb = $this->entityManager->createQueryBuilder(); + $qb->select('attachment') + ->from(Attachment::class, 'attachment') + ->where('attachment.external_path IS NOT NULL') + ->andWhere('attachment.external_path != \'\'') + ->andWhere('attachment.internal_path IS NULL'); + + $query = $qb->getQuery(); + $attachments = $query->getResult(); + + if (count($attachments) === 0) { + $io->success('No attachments with external URL found.'); + return Command::SUCCESS; + } + + $io->note('Found ' . count($attachments) . ' attachments with external URL, that will be downloaded.'); + + //If the option --private is set, the attachments will be downloaded to the private storage. + $private = $input->getOption('private'); + if ($private) { + if (!$io->confirm('Attachments will be downloaded to the private storage. Continue?')) { + return Command::SUCCESS; + } + } else { + if (!$io->confirm('Attachments will be downloaded to the public storage, where everybody knowing the correct URL can access it. Continue?')){ + return Command::SUCCESS; + } + } + + $progressBar = $io->createProgressBar(count($attachments)); + $progressBar->setFormat("%current%/%max% [%bar%] %percent:3s%% %elapsed:16s%/%estimated:-16s% \n%message%"); + + $progressBar->setMessage('Starting download...'); + $progressBar->start(); + + + $errors = []; + + foreach ($attachments as $attachment) { + /** @var Attachment $attachment */ + $progressBar->setMessage(sprintf('%s (ID: %s) from %s', $attachment->getName(), $attachment->getID(), $attachment->getHost())); + $progressBar->advance(); + + try { + $attachmentUpload = new AttachmentUpload(file: null, downloadUrl: true, private: $private); + $this->attachmentSubmitHandler->handleUpload($attachment, $attachmentUpload); + + //Write changes to the database + $this->entityManager->flush(); + } catch (AttachmentDownloadException $e) { + $errors[] = [ + 'attachment' => $attachment, + 'error' => $e->getMessage() + ]; + } + } + + $progressBar->finish(); + + //Fix the line break after the progress bar + $io->newLine(); + $io->newLine(); + + if (count($errors) > 0) { + $io->warning('Some attachments could not be downloaded:'); + foreach ($errors as $error) { + $io->warning(sprintf("Attachment %s (ID %s) could not be downloaded from %s:\n%s", + $error['attachment']->getName(), + $error['attachment']->getID(), + $error['attachment']->getExternalPath(), + $error['error']) + ); + } + } else { + $io->success('All attachments downloaded successfully.'); + } + + return Command::SUCCESS; + } +} \ No newline at end of file diff --git a/src/Command/Attachments/SanitizeSVGAttachmentsCommand.php b/src/Command/Attachments/SanitizeSVGAttachmentsCommand.php new file mode 100644 index 000000000..7f6550f0d --- /dev/null +++ b/src/Command/Attachments/SanitizeSVGAttachmentsCommand.php @@ -0,0 +1,90 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Command\Attachments; + +use App\Entity\Attachments\Attachment; +use App\Services\Attachments\AttachmentSubmitHandler; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +#[AsCommand('partdb:attachments:sanitize-svg', "Sanitize uploaded SVG files.")] +class SanitizeSVGAttachmentsCommand extends Command +{ + public function __construct(private readonly EntityManagerInterface $entityManager, private readonly AttachmentSubmitHandler $attachmentSubmitHandler, ?string $name = null) + { + parent::__construct($name); + } + + public function configure(): void + { + $this->setHelp('This command allows to sanitize SVG files uploaded via attachments. This happens automatically since version 1.17.1, this command is intended to be used for older files.'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $io->info('This command will sanitize all uploaded SVG files. This is only required if you have uploaded (untrusted) SVG files before version 1.17.1. If you are running a newer version, you don\'t need to run this command (again).'); + if (!$io->confirm('Do you want to continue?', false)) { + $io->success('Command aborted.'); + return Command::FAILURE; + } + + $io->info('Sanitizing SVG files...'); + + //Finding all attachments with svg files + $qb = $this->entityManager->createQueryBuilder(); + $qb->select('a') + ->from(Attachment::class, 'a') + ->where('a.internal_path LIKE :pattern ESCAPE \'#\'') + ->orWhere('a.original_filename LIKE :pattern ESCAPE \'#\'') + ->setParameter('pattern', '%.svg'); + + $attachments = $qb->getQuery()->getResult(); + $io->note('Found '.count($attachments).' attachments with SVG files.'); + + if (count($attachments) === 0) { + $io->success('No SVG files found.'); + return Command::FAILURE; + } + + $io->info('Sanitizing SVG files...'); + $io->progressStart(count($attachments)); + foreach ($attachments as $attachment) { + /** @var Attachment $attachment */ + $io->note('Sanitizing attachment '.$attachment->getId().' ('.($attachment->getFilename() ?? '???').')'); + $this->attachmentSubmitHandler->sanitizeSVGAttachment($attachment); + $io->progressAdvance(); + + } + $io->progressFinish(); + + $io->success('Sanitization finished. All SVG files have been sanitized.'); + return Command::SUCCESS; + } +} \ No newline at end of file diff --git a/src/Command/CheckRequirementsCommand.php b/src/Command/CheckRequirementsCommand.php index 5e15e8e2e..f9080c420 100644 --- a/src/Command/CheckRequirementsCommand.php +++ b/src/Command/CheckRequirementsCommand.php @@ -69,8 +69,8 @@ protected function checkPHP(SymfonyStyle $io, bool $only_issues = false): void if ($io->isVerbose()) { $io->comment('Checking PHP version...'); } - //We recommend PHP 8.2, but 8.1 is the minimum - if (PHP_VERSION_ID < 80200) { + //We recommend PHP 8.2, but 8.2 is the minimum + if (PHP_VERSION_ID < 80400) { $io->warning('You are using PHP '. PHP_VERSION .'. This will work, but a newer version is recommended.'); } elseif (!$only_issues) { $io->success('PHP version is sufficient.'); @@ -84,7 +84,7 @@ protected function checkPHP(SymfonyStyle $io, bool $only_issues = false): void $io->success('You are using a 64-bit system.'); } } else { - $io->warning('You are using a system with an unknown bit size. That is interesting xD'); + $io->warning(' areYou using a system with an unknown bit size. That is interesting xD'); } //Check if opcache is enabled diff --git a/src/Command/Currencies/UpdateExchangeRatesCommand.php b/src/Command/Currencies/UpdateExchangeRatesCommand.php index 0f3eb11f0..2c1f5f924 100644 --- a/src/Command/Currencies/UpdateExchangeRatesCommand.php +++ b/src/Command/Currencies/UpdateExchangeRatesCommand.php @@ -22,6 +22,7 @@ namespace App\Command\Currencies; +use App\Settings\SystemSettings\LocalizationSettings; use Symfony\Component\Console\Attribute\AsCommand; use App\Entity\PriceInformations\Currency; use App\Services\Tools\ExchangeRateUpdater; @@ -39,7 +40,7 @@ #[AsCommand('partdb:currencies:update-exchange-rates|partdb:update-exchange-rates|app:update-exchange-rates', 'Updates the currency exchange rates.')] class UpdateExchangeRatesCommand extends Command { - public function __construct(protected string $base_current, protected EntityManagerInterface $em, protected ExchangeRateUpdater $exchangeRateUpdater) + public function __construct(protected EntityManagerInterface $em, protected ExchangeRateUpdater $exchangeRateUpdater, private readonly LocalizationSettings $localizationSettings) { parent::__construct(); } @@ -54,13 +55,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io = new SymfonyStyle($input, $output); //Check for valid base current - if (3 !== strlen($this->base_current)) { + if (3 !== strlen($this->localizationSettings->baseCurrency)) { $io->error('Chosen Base current is not valid. Check your settings!'); return Command::FAILURE; } - $io->note('Update currency exchange rates with base currency: '.$this->base_current); + $io->note('Update currency exchange rates with base currency: '.$this->localizationSettings->baseCurrency); //Check what currencies we need to update: $iso_code = $input->getArgument('iso_code'); diff --git a/src/Command/Migrations/ImportPartKeeprCommand.php b/src/Command/Migrations/ImportPartKeeprCommand.php index 98272440d..429f018d5 100644 --- a/src/Command/Migrations/ImportPartKeeprCommand.php +++ b/src/Command/Migrations/ImportPartKeeprCommand.php @@ -44,7 +44,7 @@ public function __construct(protected EntityManagerInterface $em, protected MySQ protected PKDatastructureImporter $datastructureImporter, protected PKPartImporter $partImporter, protected PKImportHelper $importHelper, protected PKOptionalImporter $optionalImporter) { - parent::__construct(self::$defaultName); + parent::__construct(); } protected function configure(): void @@ -121,6 +121,11 @@ private function doImport(SymfonyStyle $io, array $data): void $count = $this->datastructureImporter->importPartUnits($data); $io->success('Imported '.$count.' measurement units.'); + //Import the custom states + $io->info('Importing custom states...'); + $count = $this->datastructureImporter->importPartCustomStates($data); + $io->success('Imported '.$count.' custom states.'); + //Import manufacturers $io->info('Importing manufacturers...'); $count = $this->datastructureImporter->importManufacturers($data); diff --git a/src/Command/User/UpgradePermissionsSchemaCommand.php b/src/Command/User/UpgradePermissionsSchemaCommand.php index 4947fd5cd..a53e21a0f 100644 --- a/src/Command/User/UpgradePermissionsSchemaCommand.php +++ b/src/Command/User/UpgradePermissionsSchemaCommand.php @@ -39,14 +39,7 @@ final class UpgradePermissionsSchemaCommand extends Command { public function __construct(private readonly PermissionSchemaUpdater $permissionSchemaUpdater, private readonly EntityManagerInterface $em, private readonly EventCommentHelper $eventCommentHelper) { - parent::__construct(self::$defaultName); - } - - protected function configure(): void - { - $this - ->setDescription(self::$defaultDescription) - ; + parent::__construct(); } protected function execute(InputInterface $input, OutputInterface $output): int diff --git a/src/Command/User/UserEnableCommand.php b/src/Command/User/UserEnableCommand.php index 00753e941..51ff2280f 100644 --- a/src/Command/User/UserEnableCommand.php +++ b/src/Command/User/UserEnableCommand.php @@ -35,7 +35,7 @@ #[AsCommand('partdb:users:enable|partdb:user:enable', 'Enables/Disable the login of one or more users')] class UserEnableCommand extends Command { - public function __construct(protected EntityManagerInterface $entityManager, string $name = null) + public function __construct(protected EntityManagerInterface $entityManager, ?string $name = null) { parent::__construct($name); } diff --git a/src/Command/User/UsersPermissionsCommand.php b/src/Command/User/UsersPermissionsCommand.php index 6408e9c9d..273823711 100644 --- a/src/Command/User/UsersPermissionsCommand.php +++ b/src/Command/User/UsersPermissionsCommand.php @@ -46,7 +46,7 @@ public function __construct(protected EntityManagerInterface $entityManager, pro { $this->userRepository = $entityManager->getRepository(User::class); - parent::__construct(self::$defaultName); + parent::__construct(); } protected function configure(): void diff --git a/src/Controller/AdminPages/BaseAdminController.php b/src/Controller/AdminPages/BaseAdminController.php index 1d19fb82f..e7dd74218 100644 --- a/src/Controller/AdminPages/BaseAdminController.php +++ b/src/Controller/AdminPages/BaseAdminController.php @@ -232,6 +232,7 @@ protected function _edit(AbstractNamedDBElement $entity, Request $request, Entit 'timeTravel' => $timeTravel_timestamp, 'repo' => $repo, 'partsContainingElement' => $repo instanceof PartsContainingRepositoryInterface, + 'showParameters' => !($this instanceof PartCustomStateController), ]); } @@ -382,6 +383,7 @@ protected function _new(Request $request, EntityManagerInterface $em, EntityImpo 'import_form' => $import_form, 'mass_creation_form' => $mass_creation_form, 'route_base' => $this->route_base, + 'showParameters' => !($this instanceof PartCustomStateController), ]); } @@ -467,6 +469,11 @@ protected function _exportAll(EntityManagerInterface $em, EntityExporter $export $this->denyAccessUnlessGranted('read', $entity); $entities = $em->getRepository($this->entity_class)->findAll(); + if (count($entities) === 0) { + $this->addFlash('error', 'entity.export.flash.error.no_entities'); + return $this->redirectToRoute($this->route_base.'_new'); + } + return $exporter->exportEntityFromRequest($entities, $request); } diff --git a/src/Controller/AdminPages/PartCustomStateController.php b/src/Controller/AdminPages/PartCustomStateController.php new file mode 100644 index 000000000..60f63abf3 --- /dev/null +++ b/src/Controller/AdminPages/PartCustomStateController.php @@ -0,0 +1,83 @@ +. + */ + +declare(strict_types=1); + +namespace App\Controller\AdminPages; + +use App\Entity\Attachments\PartCustomStateAttachment; +use App\Entity\Parameters\PartCustomStateParameter; +use App\Entity\Parts\PartCustomState; +use App\Form\AdminPages\PartCustomStateAdminForm; +use App\Services\ImportExportSystem\EntityExporter; +use App\Services\ImportExportSystem\EntityImporter; +use App\Services\Trees\StructuralElementRecursionHelper; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Attribute\Route; + +/** + * @see \App\Tests\Controller\AdminPages\PartCustomStateControllerTest + */ +#[Route(path: '/part_custom_state')] +class PartCustomStateController extends BaseAdminController +{ + protected string $entity_class = PartCustomState::class; + protected string $twig_template = 'admin/part_custom_state_admin.html.twig'; + protected string $form_class = PartCustomStateAdminForm::class; + protected string $route_base = 'part_custom_state'; + protected string $attachment_class = PartCustomStateAttachment::class; + protected ?string $parameter_class = PartCustomStateParameter::class; + + #[Route(path: '/{id}', name: 'part_custom_state_delete', methods: ['DELETE'])] + public function delete(Request $request, PartCustomState $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse + { + return $this->_delete($request, $entity, $recursionHelper); + } + + #[Route(path: '/{id}/edit/{timestamp}', name: 'part_custom_state_edit', requirements: ['id' => '\d+'])] + #[Route(path: '/{id}', requirements: ['id' => '\d+'])] + public function edit(PartCustomState $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response + { + return $this->_edit($entity, $request, $em, $timestamp); + } + + #[Route(path: '/new', name: 'part_custom_state_new')] + #[Route(path: '/{id}/clone', name: 'part_custom_state_clone')] + #[Route(path: '/')] + public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?PartCustomState $entity = null): Response + { + return $this->_new($request, $em, $importer, $entity); + } + + #[Route(path: '/export', name: 'part_custom_state_export_all')] + public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response + { + return $this->_exportAll($em, $exporter, $request); + } + + #[Route(path: '/{id}/export', name: 'part_custom_state_export')] + public function exportEntity(PartCustomState $entity, EntityExporter $exporter, Request $request): Response + { + return $this->_exportEntity($entity, $exporter, $request); + } +} diff --git a/src/Controller/AttachmentFileController.php b/src/Controller/AttachmentFileController.php index 936d27c51..81369e125 100644 --- a/src/Controller/AttachmentFileController.php +++ b/src/Controller/AttachmentFileController.php @@ -24,10 +24,12 @@ use App\DataTables\AttachmentDataTable; use App\DataTables\Filters\AttachmentFilter; +use App\DataTables\PartsDataTable; use App\Entity\Attachments\Attachment; use App\Form\Filters\AttachmentFilterType; use App\Services\Attachments\AttachmentManager; use App\Services\Trees\NodesListBuilder; +use App\Settings\BehaviorSettings\TableSettings; use Omines\DataTablesBundle\DataTableFactory; use RuntimeException; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -51,15 +53,15 @@ public function download(Attachment $attachment, AttachmentManager $helper): Bin $this->denyAccessUnlessGranted('show_private', $attachment); } - if ($attachment->isExternal()) { - throw $this->createNotFoundException('The file for this attachment is external and can not stored locally!'); + if (!$attachment->hasInternal()) { + throw $this->createNotFoundException('The file for this attachment is external and not stored locally!'); } - if (!$helper->isFileExisting($attachment)) { + if (!$helper->isInternalFileExisting($attachment)) { throw $this->createNotFoundException('The file associated with the attachment is not existing!'); } - $file_path = $helper->toAbsoluteFilePath($attachment); + $file_path = $helper->toAbsoluteInternalFilePath($attachment); $response = new BinaryFileResponse($file_path); //Set header content disposition, so that the file will be downloaded @@ -80,15 +82,15 @@ public function view(Attachment $attachment, AttachmentManager $helper): BinaryF $this->denyAccessUnlessGranted('show_private', $attachment); } - if ($attachment->isExternal()) { - throw $this->createNotFoundException('The file for this attachment is external and can not stored locally!'); + if (!$attachment->hasInternal()) { + throw $this->createNotFoundException('The file for this attachment is external and not stored locally!'); } - if (!$helper->isFileExisting($attachment)) { + if (!$helper->isInternalFileExisting($attachment)) { throw $this->createNotFoundException('The file associated with the attachment is not existing!'); } - $file_path = $helper->toAbsoluteFilePath($attachment); + $file_path = $helper->toAbsoluteInternalFilePath($attachment); $response = new BinaryFileResponse($file_path); //Set header content disposition, so that the file will be downloaded @@ -98,7 +100,8 @@ public function view(Attachment $attachment, AttachmentManager $helper): BinaryF } #[Route(path: '/attachment/list', name: 'attachment_list')] - public function attachmentsTable(Request $request, DataTableFactory $dataTableFactory, NodesListBuilder $nodesListBuilder): Response + public function attachmentsTable(Request $request, DataTableFactory $dataTableFactory, NodesListBuilder $nodesListBuilder, + TableSettings $tableSettings): Response { $this->denyAccessUnlessGranted('@attachments.list_attachments'); @@ -110,7 +113,7 @@ public function attachmentsTable(Request $request, DataTableFactory $dataTableFa $filterForm->handleRequest($formRequest); - $table = $dataTableFactory->createFromType(AttachmentDataTable::class, ['filter' => $filter]) + $table = $dataTableFactory->createFromType(AttachmentDataTable::class, ['filter' => $filter], ['pageLength' => $tableSettings->fullDefaultPageSize, 'lengthMenu' => PartsDataTable::LENGTH_MENU]) ->handleRequest($request); if ($table->isCallback()) { diff --git a/src/Controller/BulkInfoProviderImportController.php b/src/Controller/BulkInfoProviderImportController.php new file mode 100644 index 000000000..2d3dd7f6a --- /dev/null +++ b/src/Controller/BulkInfoProviderImportController.php @@ -0,0 +1,588 @@ +. + */ + +declare(strict_types=1); + +namespace App\Controller; + +use App\Entity\InfoProviderSystem\BulkImportJobStatus; +use App\Entity\InfoProviderSystem\BulkInfoProviderImportJob; +use App\Entity\InfoProviderSystem\BulkInfoProviderImportJobPart; +use App\Entity\Parts\Part; +use App\Entity\Parts\Supplier; +use App\Entity\UserSystem\User; +use App\Form\InfoProviderSystem\GlobalFieldMappingType; +use App\Services\InfoProviderSystem\BulkInfoProviderService; +use App\Services\InfoProviderSystem\DTOs\BulkSearchFieldMappingDTO; +use App\Services\InfoProviderSystem\DTOs\BulkSearchPartResultsDTO; +use App\Services\InfoProviderSystem\DTOs\BulkSearchResponseDTO; +use Doctrine\ORM\EntityManagerInterface; +use Psr\Log\LoggerInterface; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Attribute\Route; + +#[Route('/tools/bulk_info_provider_import')] +class BulkInfoProviderImportController extends AbstractController +{ + public function __construct( + private readonly BulkInfoProviderService $bulkService, + private readonly EntityManagerInterface $entityManager, + private readonly LoggerInterface $logger, + #[Autowire(param: 'partdb.bulk_import.batch_size')] + private readonly int $bulkImportBatchSize, + #[Autowire(param: 'partdb.bulk_import.max_parts_per_operation')] + private readonly int $bulkImportMaxParts + ) { + } + + /** + * Convert field mappings from array format to FieldMappingDTO[]. + * + * @param array $fieldMappings Array of field mapping arrays + * @return BulkSearchFieldMappingDTO[] Array of FieldMappingDTO objects + */ + private function convertFieldMappingsToDto(array $fieldMappings): array + { + $dtos = []; + foreach ($fieldMappings as $mapping) { + $dtos[] = new BulkSearchFieldMappingDTO(field: $mapping['field'], providers: $mapping['providers'], priority: $mapping['priority'] ?? 1); + } + return $dtos; + } + + private function createErrorResponse(string $message, int $statusCode = 400, array $context = []): JsonResponse + { + $this->logger->warning('Bulk import operation failed', array_merge([ + 'error' => $message, + 'user' => $this->getUser()?->getUserIdentifier(), + ], $context)); + + return $this->json([ + 'success' => false, + 'error' => $message + ], $statusCode); + } + + private function validateJobAccess(int $jobId): ?BulkInfoProviderImportJob + { + $this->denyAccessUnlessGranted('@info_providers.create_parts'); + + $job = $this->entityManager->getRepository(BulkInfoProviderImportJob::class)->find($jobId); + + if (!$job) { + return null; + } + + if ($job->getCreatedBy() !== $this->getUser()) { + return null; + } + + return $job; + } + + private function updatePartSearchResults(BulkInfoProviderImportJob $job, ?BulkSearchPartResultsDTO $newResults): void + { + if ($newResults === null) { + return; + } + + // Only deserialize and update if we have new results + $allResults = $job->getSearchResults($this->entityManager); + + // Find and update the results for this specific part + $allResults = $allResults->replaceResultsForPart($newResults); + + // Save updated results back to job + $job->setSearchResults($allResults); + } + + #[Route('/step1', name: 'bulk_info_provider_step1')] + public function step1(Request $request): Response + { + $this->denyAccessUnlessGranted('@info_providers.create_parts'); + + set_time_limit(600); + + $ids = $request->query->get('ids'); + if (!$ids) { + $this->addFlash('error', 'No parts selected for bulk import'); + return $this->redirectToRoute('homepage'); + } + + $partIds = explode(',', $ids); + $partRepository = $this->entityManager->getRepository(Part::class); + $parts = $partRepository->getElementsFromIDArray($partIds); + + if (empty($parts)) { + $this->addFlash('error', 'No valid parts found for bulk import'); + return $this->redirectToRoute('homepage'); + } + + // Validate against configured maximum + if (count($parts) > $this->bulkImportMaxParts) { + $this->addFlash('error', sprintf( + 'Too many parts selected (%d). Maximum allowed is %d parts per operation.', + count($parts), + $this->bulkImportMaxParts + )); + return $this->redirectToRoute('homepage'); + } + + if (count($parts) > ($this->bulkImportMaxParts / 2)) { + $this->addFlash('warning', 'Processing ' . count($parts) . ' parts may take several minutes and could timeout. Consider processing smaller batches.'); + } + + // Generate field choices + $fieldChoices = [ + 'info_providers.bulk_search.field.mpn' => 'mpn', + 'info_providers.bulk_search.field.name' => 'name', + ]; + + // Add dynamic supplier fields + $suppliers = $this->entityManager->getRepository(Supplier::class)->findAll(); + foreach ($suppliers as $supplier) { + $supplierKey = strtolower(str_replace([' ', '-', '_'], '_', $supplier->getName())); + $fieldChoices["Supplier: " . $supplier->getName() . " (SPN)"] = $supplierKey . '_spn'; + } + + // Initialize form with useful default mappings + $initialData = [ + 'field_mappings' => [ + ['field' => 'mpn', 'providers' => [], 'priority' => 1] + ], + 'prefetch_details' => false + ]; + + $form = $this->createForm(GlobalFieldMappingType::class, $initialData, [ + 'field_choices' => $fieldChoices + ]); + $form->handleRequest($request); + + $searchResults = null; + + if ($form->isSubmitted() && $form->isValid()) { + $formData = $form->getData(); + $fieldMappingDtos = $this->convertFieldMappingsToDto($formData['field_mappings']); + $prefetchDetails = $formData['prefetch_details'] ?? false; + + $user = $this->getUser(); + if (!$user instanceof User) { + throw new \RuntimeException('User must be authenticated and of type User'); + } + + // Validate part count against configuration limit + if (count($parts) > $this->bulkImportMaxParts) { + $this->addFlash('error', "Too many parts selected. Maximum allowed: {$this->bulkImportMaxParts}"); + $partIds = array_map(fn($part) => $part->getId(), $parts); + return $this->redirectToRoute('bulk_info_provider_step1', ['ids' => implode(',', $partIds)]); + } + + // Create and save the job + $job = new BulkInfoProviderImportJob(); + $job->setFieldMappings($fieldMappingDtos); + $job->setPrefetchDetails($prefetchDetails); + $job->setCreatedBy($user); + + foreach ($parts as $part) { + $jobPart = new BulkInfoProviderImportJobPart($job, $part); + $job->addJobPart($jobPart); + } + + $this->entityManager->persist($job); + $this->entityManager->flush(); + + try { + $searchResultsDto = $this->bulkService->performBulkSearch($parts, $fieldMappingDtos, $prefetchDetails); + + // Save search results to job + $job->setSearchResults($searchResultsDto); + $job->markAsInProgress(); + $this->entityManager->flush(); + + // Prefetch details if requested + if ($prefetchDetails) { + $this->bulkService->prefetchDetailsForResults($searchResultsDto); + } + + return $this->redirectToRoute('bulk_info_provider_step2', ['jobId' => $job->getId()]); + + } catch (\Exception $e) { + $this->logger->error('Critical error during bulk import search', [ + 'job_id' => $job->getId(), + 'error' => $e->getMessage(), + 'exception' => $e + ]); + + $this->entityManager->remove($job); + $this->entityManager->flush(); + + $this->addFlash('error', 'Search failed due to an error: ' . $e->getMessage()); + $partIds = array_map(fn($part) => $part->getId(), $parts); + return $this->redirectToRoute('bulk_info_provider_step1', ['ids' => implode(',', $partIds)]); + } + } + + // Get existing in-progress jobs for current user + $existingJobs = $this->entityManager->getRepository(BulkInfoProviderImportJob::class) + ->findBy(['createdBy' => $this->getUser(), 'status' => BulkImportJobStatus::IN_PROGRESS], ['createdAt' => 'DESC'], 10); + + return $this->render('info_providers/bulk_import/step1.html.twig', [ + 'form' => $form, + 'parts' => $parts, + 'search_results' => $searchResults, + 'existing_jobs' => $existingJobs, + 'fieldChoices' => $fieldChoices + ]); + } + + #[Route('/manage', name: 'bulk_info_provider_manage')] + public function manageBulkJobs(): Response + { + $this->denyAccessUnlessGranted('@info_providers.create_parts'); + + // Get all jobs for current user + $allJobs = $this->entityManager->getRepository(BulkInfoProviderImportJob::class) + ->findBy([], ['createdAt' => 'DESC']); + + // Check and auto-complete jobs that should be completed + // Also clean up jobs with no results (failed searches) + $updatedJobs = false; + $jobsToDelete = []; + + foreach ($allJobs as $job) { + if ($job->isAllPartsCompleted() && !$job->isCompleted()) { + $job->markAsCompleted(); + $updatedJobs = true; + } + + // Mark jobs with no results for deletion (failed searches) + if ($job->getResultCount() === 0 && $job->isInProgress()) { + $jobsToDelete[] = $job; + } + } + + // Delete failed jobs + foreach ($jobsToDelete as $job) { + $this->entityManager->remove($job); + $updatedJobs = true; + } + + // Flush changes if any jobs were updated + if ($updatedJobs) { + $this->entityManager->flush(); + + if (!empty($jobsToDelete)) { + $this->addFlash('info', 'Cleaned up ' . count($jobsToDelete) . ' failed job(s) with no results.'); + } + } + + return $this->render('info_providers/bulk_import/manage.html.twig', [ + 'jobs' => $this->entityManager->getRepository(BulkInfoProviderImportJob::class) + ->findBy([], ['createdAt' => 'DESC']) // Refetch after cleanup + ]); + } + + #[Route('/job/{jobId}/delete', name: 'bulk_info_provider_delete', methods: ['DELETE'])] + public function deleteJob(int $jobId): Response + { + $job = $this->validateJobAccess($jobId); + if (!$job) { + return $this->createErrorResponse('Job not found or access denied', 404, ['job_id' => $jobId]); + } + + // Only allow deletion of completed, failed, or stopped jobs + if (!$job->isCompleted() && !$job->isFailed() && !$job->isStopped()) { + return $this->json(['error' => 'Cannot delete active job'], 400); + } + + $this->entityManager->remove($job); + $this->entityManager->flush(); + + return $this->json(['success' => true]); + } + + #[Route('/job/{jobId}/stop', name: 'bulk_info_provider_stop', methods: ['POST'])] + public function stopJob(int $jobId): Response + { + $job = $this->validateJobAccess($jobId); + if (!$job) { + return $this->createErrorResponse('Job not found or access denied', 404, ['job_id' => $jobId]); + } + + // Only allow stopping of pending or in-progress jobs + if (!$job->canBeStopped()) { + return $this->json(['error' => 'Cannot stop job in current status'], 400); + } + + $job->markAsStopped(); + $this->entityManager->flush(); + + return $this->json(['success' => true]); + } + + + #[Route('/step2/{jobId}', name: 'bulk_info_provider_step2')] + public function step2(int $jobId): Response + { + $this->denyAccessUnlessGranted('@info_providers.create_parts'); + + $job = $this->entityManager->getRepository(BulkInfoProviderImportJob::class)->find($jobId); + + if (!$job) { + $this->addFlash('error', 'Bulk import job not found'); + return $this->redirectToRoute('bulk_info_provider_step1'); + } + + // Check if user owns this job + if ($job->getCreatedBy() !== $this->getUser()) { + $this->addFlash('error', 'Access denied to this bulk import job'); + return $this->redirectToRoute('bulk_info_provider_step1'); + } + + // Get the parts and deserialize search results + $parts = $job->getJobParts()->map(fn($jobPart) => $jobPart->getPart())->toArray(); + $searchResults = $job->getSearchResults($this->entityManager); + + return $this->render('info_providers/bulk_import/step2.html.twig', [ + 'job' => $job, + 'parts' => $parts, + 'search_results' => $searchResults, + ]); + } + + + #[Route('/job/{jobId}/part/{partId}/mark-completed', name: 'bulk_info_provider_mark_completed', methods: ['POST'])] + public function markPartCompleted(int $jobId, int $partId): Response + { + $job = $this->validateJobAccess($jobId); + if (!$job) { + return $this->createErrorResponse('Job not found or access denied', 404, ['job_id' => $jobId]); + } + + $job->markPartAsCompleted($partId); + + // Auto-complete job if all parts are done + if ($job->isAllPartsCompleted() && !$job->isCompleted()) { + $job->markAsCompleted(); + } + + $this->entityManager->flush(); + + return $this->json([ + 'success' => true, + 'progress' => $job->getProgressPercentage(), + 'completed_count' => $job->getCompletedPartsCount(), + 'total_count' => $job->getPartCount(), + 'job_completed' => $job->isCompleted() + ]); + } + + #[Route('/job/{jobId}/part/{partId}/mark-skipped', name: 'bulk_info_provider_mark_skipped', methods: ['POST'])] + public function markPartSkipped(int $jobId, int $partId, Request $request): Response + { + $job = $this->validateJobAccess($jobId); + if (!$job) { + return $this->createErrorResponse('Job not found or access denied', 404, ['job_id' => $jobId]); + } + + $reason = $request->request->get('reason', ''); + $job->markPartAsSkipped($partId, $reason); + + // Auto-complete job if all parts are done + if ($job->isAllPartsCompleted() && !$job->isCompleted()) { + $job->markAsCompleted(); + } + + $this->entityManager->flush(); + + return $this->json([ + 'success' => true, + 'progress' => $job->getProgressPercentage(), + 'completed_count' => $job->getCompletedPartsCount(), + 'skipped_count' => $job->getSkippedPartsCount(), + 'total_count' => $job->getPartCount(), + 'job_completed' => $job->isCompleted() + ]); + } + + #[Route('/job/{jobId}/part/{partId}/mark-pending', name: 'bulk_info_provider_mark_pending', methods: ['POST'])] + public function markPartPending(int $jobId, int $partId): Response + { + $job = $this->validateJobAccess($jobId); + if (!$job) { + return $this->createErrorResponse('Job not found or access denied', 404, ['job_id' => $jobId]); + } + + $job->markPartAsPending($partId); + $this->entityManager->flush(); + + return $this->json([ + 'success' => true, + 'progress' => $job->getProgressPercentage(), + 'completed_count' => $job->getCompletedPartsCount(), + 'skipped_count' => $job->getSkippedPartsCount(), + 'total_count' => $job->getPartCount(), + 'job_completed' => $job->isCompleted() + ]); + } + + #[Route('/job/{jobId}/part/{partId}/research', name: 'bulk_info_provider_research_part', methods: ['POST'])] + public function researchPart(int $jobId, int $partId): JsonResponse + { + $job = $this->validateJobAccess($jobId); + if (!$job) { + return $this->createErrorResponse('Job not found or access denied', 404, ['job_id' => $jobId]); + } + + $part = $this->entityManager->getRepository(Part::class)->find($partId); + if (!$part) { + return $this->createErrorResponse('Part not found', 404, ['part_id' => $partId]); + } + + // Only refresh if the entity might be stale (optional optimization) + if ($this->entityManager->getUnitOfWork()->isScheduledForUpdate($part)) { + $this->entityManager->refresh($part); + } + + try { + // Use the job's field mappings to perform the search + $fieldMappingDtos = $job->getFieldMappings(); + $prefetchDetails = $job->isPrefetchDetails(); + + try { + $searchResultsDto = $this->bulkService->performBulkSearch([$part], $fieldMappingDtos, $prefetchDetails); + } catch (\Exception $searchException) { + // Handle "no search results found" as a normal case, not an error + if (str_contains($searchException->getMessage(), 'No search results found')) { + $searchResultsDto = null; + } else { + throw $searchException; + } + } + + // Update the job's search results for this specific part efficiently + $this->updatePartSearchResults($job, $searchResultsDto[0] ?? null); + + // Prefetch details if requested + if ($prefetchDetails && $searchResultsDto !== null) { + $this->bulkService->prefetchDetailsForResults($searchResultsDto); + } + + $this->entityManager->flush(); + + // Return the new results for this part + $newResults = $searchResultsDto[0] ?? null; + + return $this->json([ + 'success' => true, + 'part_id' => $partId, + 'results_count' => $newResults ? $newResults->getResultCount() : 0, + 'errors_count' => $newResults ? $newResults->getErrorCount() : 0, + 'message' => 'Part research completed successfully' + ]); + + } catch (\Exception $e) { + return $this->createErrorResponse( + 'Research failed: ' . $e->getMessage(), + 500, + [ + 'job_id' => $jobId, + 'part_id' => $partId, + 'exception' => $e->getMessage() + ] + ); + } + } + + #[Route('/job/{jobId}/research-all', name: 'bulk_info_provider_research_all', methods: ['POST'])] + public function researchAllParts(int $jobId): JsonResponse + { + $job = $this->validateJobAccess($jobId); + if (!$job) { + return $this->createErrorResponse('Job not found or access denied', 404, ['job_id' => $jobId]); + } + + // Get all parts that are not completed or skipped + $parts = []; + foreach ($job->getJobParts() as $jobPart) { + if (!$jobPart->isCompleted() && !$jobPart->isSkipped()) { + $parts[] = $jobPart->getPart(); + } + } + + if (empty($parts)) { + return $this->json([ + 'success' => true, + 'message' => 'No parts to research', + 'researched_count' => 0 + ]); + } + + try { + $fieldMappingDtos = $job->getFieldMappings(); + $prefetchDetails = $job->isPrefetchDetails(); + + // Process in batches to reduce memory usage for large operations + $allResults = new BulkSearchResponseDTO(partResults: []); + $batches = array_chunk($parts, $this->bulkImportBatchSize); + + foreach ($batches as $batch) { + $batchResultsDto = $this->bulkService->performBulkSearch($batch, $fieldMappingDtos, $prefetchDetails); + $allResults = BulkSearchResponseDTO::merge($allResults, $batchResultsDto); + + // Properly manage entity manager memory without losing state + $jobId = $job->getId(); + //$this->entityManager->clear(); //TODO: This seems to cause problems with the user relation, when trying to flush later + $job = $this->entityManager->find(BulkInfoProviderImportJob::class, $jobId); + } + + // Update the job's search results + $job->setSearchResults($allResults); + + // Prefetch details if requested + if ($prefetchDetails) { + $this->bulkService->prefetchDetailsForResults($allResults); + } + + $this->entityManager->flush(); + + return $this->json([ + 'success' => true, + 'researched_count' => count($parts), + 'message' => sprintf('Successfully researched %d parts', count($parts)) + ]); + + } catch (\Exception $e) { + return $this->createErrorResponse( + 'Bulk research failed: ' . $e->getMessage(), + 500, + [ + 'job_id' => $jobId, + 'part_count' => count($parts), + 'exception' => $e->getMessage() + ] + ); + } + } +} diff --git a/src/Controller/InfoProviderController.php b/src/Controller/InfoProviderController.php index a6ce3f1bf..b79c307cf 100644 --- a/src/Controller/InfoProviderController.php +++ b/src/Controller/InfoProviderController.php @@ -25,14 +25,20 @@ use App\Entity\Parts\Manufacturer; use App\Entity\Parts\Part; +use App\Exceptions\OAuthReconnectRequiredException; use App\Form\InfoProviderSystem\PartSearchType; use App\Services\InfoProviderSystem\ExistingPartFinder; use App\Services\InfoProviderSystem\PartInfoRetriever; use App\Services\InfoProviderSystem\ProviderRegistry; +use App\Settings\AppSettings; +use App\Settings\InfoProviderSystem\InfoProviderGeneralSettings; use Doctrine\ORM\EntityManagerInterface; +use Jbtronics\SettingsBundle\Form\SettingsFormFactoryInterface; +use Jbtronics\SettingsBundle\Manager\SettingsManagerInterface; use Psr\Log\LoggerInterface; use Symfony\Bridge\Doctrine\Attribute\MapEntity; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\HttpClient\Exception\ClientException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -46,7 +52,9 @@ class InfoProviderController extends AbstractController public function __construct(private readonly ProviderRegistry $providerRegistry, private readonly PartInfoRetriever $infoRetriever, - private readonly ExistingPartFinder $existingPartFinder + private readonly ExistingPartFinder $existingPartFinder, + private readonly SettingsManagerInterface $settingsManager, + private readonly SettingsFormFactoryInterface $settingsFormFactory ) { @@ -63,9 +71,51 @@ public function listProviders(): Response ]); } + #[Route('/provider/{provider}/settings', name: 'info_providers_provider_settings')] + public function providerSettings(string $provider, Request $request): Response + { + $this->denyAccessUnlessGranted('@config.change_system_settings'); + $this->denyAccessUnlessGranted('@info_providers.create_parts'); + + $providerInstance = $this->providerRegistry->getProviderByKey($provider); + $settingsClass = $providerInstance->getProviderInfo()['settings_class'] ?? throw new \LogicException('Provider ' . $provider . ' does not have a settings class defined'); + + //Create a clone of the settings object + $settings = $this->settingsManager->createTemporaryCopy($settingsClass); + + //Create a form builder for the settings object + $builder = $this->settingsFormFactory->createSettingsFormBuilder($settings); + + //Add a submit button to the form + $builder->add('submit', SubmitType::class, ['label' => 'save']); + + //Create the form + $form = $builder->getForm(); + $form->handleRequest($request); + + //If the form was submitted and is valid, save the settings + if ($form->isSubmitted() && $form->isValid()) { + $this->settingsManager->mergeTemporaryCopy($settings); + $this->settingsManager->save($settings); + + $this->addFlash('success', t('settings.flash.saved')); + } + + if ($form->isSubmitted() && !$form->isValid()) { + $this->addFlash('error', t('settings.flash.invalid')); + } + + //Render the form + return $this->render('info_providers/settings/provider_settings.html.twig', [ + 'form' => $form, + 'info_provider_key' => $provider, + 'info_provider_info' => $providerInstance->getProviderInfo(), + ]); + } + #[Route('/search', name: 'info_providers_search')] #[Route('/update/{target}', name: 'info_providers_update_part_search')] - public function search(Request $request, #[MapEntity(id: 'target')] ?Part $update_target, LoggerInterface $exceptionLogger): Response + public function search(Request $request, #[MapEntity(id: 'target')] ?Part $update_target, LoggerInterface $exceptionLogger, InfoProviderGeneralSettings $infoProviderSettings): Response { $this->denyAccessUnlessGranted('@info_providers.create_parts'); @@ -96,6 +146,23 @@ public function search(Request $request, #[MapEntity(id: 'target')] ?Part $updat } } + //If the providers form is still empty, use our default value from the settings + if (count($form->get('providers')->getData() ?? []) === 0) { + $default_providers = $infoProviderSettings->defaultSearchProviders; + $provider_objects = []; + foreach ($default_providers as $provider_key) { + try { + $tmp = $this->providerRegistry->getProviderByKey($provider_key); + if ($tmp->isActive()) { + $provider_objects[] = $tmp; + } + } catch (\InvalidArgumentException $e) { + //If the provider is not found, just ignore it + } + } + $form->get('providers')->setData($provider_objects); + } + if ($form->isSubmitted() && $form->isValid()) { $keyword = $form->get('keyword')->getData(); $providers = $form->get('providers')->getData(); @@ -109,8 +176,11 @@ public function search(Request $request, #[MapEntity(id: 'target')] ?Part $updat $this->addFlash('error',$e->getMessage()); //Log the exception $exceptionLogger->error('Error during info provider search: ' . $e->getMessage(), ['exception' => $e]); + } catch (OAuthReconnectRequiredException $e) { + $this->addFlash('error', t('info_providers.search.error.oauth_reconnect', ['%provider%' => $e->getProviderName()])); } + // modify the array to an array of arrays that has a field for a matching local Part // the advantage to use that format even when we don't look for local parts is that we // always work with the same interface @@ -128,4 +198,4 @@ public function search(Request $request, #[MapEntity(id: 'target')] ?Part $updat 'update_target' => $update_target ]); } -} \ No newline at end of file +} diff --git a/src/Controller/LabelController.php b/src/Controller/LabelController.php index 4950628bf..90a6715b8 100644 --- a/src/Controller/LabelController.php +++ b/src/Controller/LabelController.php @@ -58,12 +58,15 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Contracts\Translation\TranslatorInterface; #[Route(path: '/label')] class LabelController extends AbstractController { - public function __construct(protected LabelGenerator $labelGenerator, protected EntityManagerInterface $em, protected ElementTypeNameGenerator $elementTypeNameGenerator, protected RangeParser $rangeParser, protected TranslatorInterface $translator) + public function __construct(protected LabelGenerator $labelGenerator, protected EntityManagerInterface $em, protected ElementTypeNameGenerator $elementTypeNameGenerator, protected RangeParser $rangeParser, protected TranslatorInterface $translator, + private readonly ValidatorInterface $validator + ) { } @@ -85,6 +88,7 @@ public function generator(Request $request, ?LabelProfile $profile = null): Resp $form = $this->createForm(LabelDialogType::class, null, [ 'disable_options' => $disable_options, + 'profile' => $profile ]); //Try to parse given target_type and target_id @@ -120,12 +124,49 @@ public function generator(Request $request, ?LabelProfile $profile = null): Resp goto render; } - $profile = new LabelProfile(); - $profile->setName($form->get('save_profile_name')->getData()); + $new_profile = new LabelProfile(); + $new_profile->setName($form->get('save_profile_name')->getData()); + $new_profile->setOptions($form_options); + + //Validate the profile name + $errors = $this->validator->validate($new_profile); + if (count($errors) > 0) { + foreach ($errors as $error) { + $form->get('save_profile_name')->addError(new FormError($error->getMessage())); + } + goto render; + } + + $this->em->persist($new_profile); + $this->em->flush(); + $this->addFlash('success', 'label_generator.profile_saved'); + + return $this->redirectToRoute('label_dialog_profile', [ + 'profile' => $new_profile->getID(), + 'target_id' => (string) $form->get('target_id')->getData() + ]); + } + + //Check if the current profile should be updated + if ($form->has('update_profile') + && $form->get('update_profile')->isClicked() //@phpstan-ignore-line Phpstan does not recognize the isClicked method + && $profile instanceof LabelProfile + && $this->isGranted('edit', $profile)) { + //Update the profile options $profile->setOptions($form_options); + + //Validate the profile name + $errors = $this->validator->validate($profile); + if (count($errors) > 0) { + foreach ($errors as $error) { + $this->addFlash('error', $error->getMessage()); + } + goto render; + } + $this->em->persist($profile); $this->em->flush(); - $this->addFlash('success', 'label_generator.profile_saved'); + $this->addFlash('success', 'label_generator.profile_updated'); return $this->redirectToRoute('label_dialog_profile', [ 'profile' => $profile->getID(), diff --git a/src/Controller/LogController.php b/src/Controller/LogController.php index a849539db..8aed44e8f 100644 --- a/src/Controller/LogController.php +++ b/src/Controller/LogController.php @@ -38,6 +38,7 @@ use App\Services\LogSystem\LogLevelHelper; use App\Services\LogSystem\LogTargetHelper; use App\Services\LogSystem\TimeTravel; +use App\Settings\BehaviorSettings\TableSettings; use Doctrine\ORM\EntityManagerInterface; use InvalidArgumentException; use Omines\DataTablesBundle\DataTableFactory; @@ -58,7 +59,7 @@ public function __construct(protected EntityManagerInterface $entityManager, pro } #[Route(path: '/', name: 'log_view')] - public function showLogs(Request $request, DataTableFactory $dataTable): Response + public function showLogs(Request $request, DataTableFactory $dataTable, TableSettings $tableSettings): Response { $this->denyAccessUnlessGranted('@system.show_logs'); @@ -72,7 +73,7 @@ public function showLogs(Request $request, DataTableFactory $dataTable): Respons $table = $dataTable->createFromType(LogDataTable::class, [ 'filter' => $filter, - ]) + ], ['pageLength' => $tableSettings->fullDefaultPageSize]) ->handleRequest($request); if ($table->isCallback()) { diff --git a/src/Controller/PartController.php b/src/Controller/PartController.php index b11a5c900..aeb2664ed 100644 --- a/src/Controller/PartController.php +++ b/src/Controller/PartController.php @@ -46,6 +46,7 @@ use App\Services\Parts\PartLotWithdrawAddHelper; use App\Services\Parts\PricedetailHelper; use App\Services\ProjectSystem\ProjectBuildPartHelper; +use App\Settings\BehaviorSettings\PartInfoSettings; use DateTime; use Doctrine\ORM\EntityManagerInterface; use Exception; @@ -63,14 +64,17 @@ use function Symfony\Component\Translation\t; #[Route(path: '/part')] -class PartController extends AbstractController +final class PartController extends AbstractController { - public function __construct(protected PricedetailHelper $pricedetailHelper, - protected PartPreviewGenerator $partPreviewGenerator, + public function __construct( + private readonly PricedetailHelper $pricedetailHelper, + private readonly PartPreviewGenerator $partPreviewGenerator, private readonly TranslatorInterface $translator, - private readonly AttachmentSubmitHandler $attachmentSubmitHandler, private readonly EntityManagerInterface $em, - protected EventCommentHelper $commentHelper) - { + private readonly AttachmentSubmitHandler $attachmentSubmitHandler, + private readonly EntityManagerInterface $em, + private readonly EventCommentHelper $commentHelper, + private readonly PartInfoSettings $partInfoSettings, + ) { } /** @@ -79,9 +83,16 @@ public function __construct(protected PricedetailHelper $pricedetailHelper, */ #[Route(path: '/{id}/info/{timestamp}', name: 'part_info')] #[Route(path: '/{id}', requirements: ['id' => '\d+'])] - public function show(Part $part, Request $request, TimeTravel $timeTravel, HistoryHelper $historyHelper, - DataTableFactory $dataTable, ParameterExtractor $parameterExtractor, PartLotWithdrawAddHelper $withdrawAddHelper, ?string $timestamp = null): Response - { + public function show( + Part $part, + Request $request, + TimeTravel $timeTravel, + HistoryHelper $historyHelper, + DataTableFactory $dataTable, + ParameterExtractor $parameterExtractor, + PartLotWithdrawAddHelper $withdrawAddHelper, + ?string $timestamp = null + ): Response { $this->denyAccessUnlessGranted('read', $part); $timeTravel_timestamp = null; @@ -119,8 +130,8 @@ public function show(Part $part, Request $request, TimeTravel $timeTravel, Histo 'pricedetail_helper' => $this->pricedetailHelper, 'pictures' => $this->partPreviewGenerator->getPreviewAttachments($part), 'timeTravel' => $timeTravel_timestamp, - 'description_params' => $parameterExtractor->extractParameters($part->getDescription()), - 'comment_params' => $parameterExtractor->extractParameters($part->getComment()), + 'description_params' => $this->partInfoSettings->extractParamsFromDescription ? $parameterExtractor->extractParameters($part->getDescription()) : [], + 'comment_params' => $this->partInfoSettings->extractParamsFromNotes ? $parameterExtractor->extractParameters($part->getComment()) : [], 'withdraw_add_helper' => $withdrawAddHelper, ] ); @@ -131,7 +142,43 @@ public function edit(Part $part, Request $request): Response { $this->denyAccessUnlessGranted('edit', $part); - return $this->renderPartForm('edit', $request, $part); + // Check if this is part of a bulk import job + $jobId = $request->query->get('jobId'); + $bulkJob = null; + if ($jobId) { + $bulkJob = $this->em->getRepository(\App\Entity\InfoProviderSystem\BulkInfoProviderImportJob::class)->find($jobId); + // Verify user owns this job + if ($bulkJob && $bulkJob->getCreatedBy() !== $this->getUser()) { + $bulkJob = null; + } + } + + return $this->renderPartForm('edit', $request, $part, [], [ + 'bulk_job' => $bulkJob + ]); + } + + #[Route(path: '/{id}/bulk-import-complete/{jobId}', name: 'part_bulk_import_complete', methods: ['POST'])] + public function markBulkImportComplete(Part $part, int $jobId, Request $request): Response + { + $this->denyAccessUnlessGranted('edit', $part); + + if (!$this->isCsrfTokenValid('bulk_complete_' . $part->getId(), $request->request->get('_token'))) { + throw $this->createAccessDeniedException('Invalid CSRF token'); + } + + $bulkJob = $this->em->getRepository(\App\Entity\InfoProviderSystem\BulkInfoProviderImportJob::class)->find($jobId); + if (!$bulkJob || $bulkJob->getCreatedBy() !== $this->getUser()) { + throw $this->createNotFoundException('Bulk import job not found'); + } + + $bulkJob->markPartAsCompleted($part->getId()); + $this->em->persist($bulkJob); + $this->em->flush(); + + $this->addFlash('success', 'Part marked as completed in bulk import'); + + return $this->redirectToRoute('bulk_info_provider_step2', ['jobId' => $jobId]); } #[Route(path: '/{id}/delete', name: 'part_delete', methods: ['DELETE'])] @@ -139,7 +186,7 @@ public function delete(Request $request, Part $part): RedirectResponse { $this->denyAccessUnlessGranted('delete', $part); - if ($this->isCsrfTokenValid('delete'.$part->getID(), $request->request->get('_token'))) { + if ($this->isCsrfTokenValid('delete' . $part->getID(), $request->request->get('_token'))) { $this->commentHelper->setMessage($request->request->get('log_comment', null)); @@ -158,11 +205,15 @@ public function delete(Request $request, Part $part): RedirectResponse #[Route(path: '/new', name: 'part_new')] #[Route(path: '/{id}/clone', name: 'part_clone')] #[Route(path: '/new_build_part/{project_id}', name: 'part_new_build_part')] - public function new(Request $request, EntityManagerInterface $em, TranslatorInterface $translator, - AttachmentSubmitHandler $attachmentSubmitHandler, ProjectBuildPartHelper $projectBuildPartHelper, + public function new( + Request $request, + EntityManagerInterface $em, + TranslatorInterface $translator, + AttachmentSubmitHandler $attachmentSubmitHandler, + ProjectBuildPartHelper $projectBuildPartHelper, #[MapEntity(mapping: ['id' => 'id'])] ?Part $part = null, - #[MapEntity(mapping: ['project_id' => 'id'])] ?Project $project = null): Response - { + #[MapEntity(mapping: ['project_id' => 'id'])] ?Project $project = null + ): Response { if ($part instanceof Part) { //Clone part @@ -257,9 +308,14 @@ public function merge(Request $request, Part $target, Part $other, PartMerger $p } #[Route(path: '/{id}/from_info_provider/{providerKey}/{providerId}/update', name: 'info_providers_update_part', requirements: ['providerId' => '.+'])] - public function updateFromInfoProvider(Part $part, Request $request, string $providerKey, string $providerId, - PartInfoRetriever $infoRetriever, PartMerger $partMerger): Response - { + public function updateFromInfoProvider( + Part $part, + Request $request, + string $providerKey, + string $providerId, + PartInfoRetriever $infoRetriever, + PartMerger $partMerger + ): Response { $this->denyAccessUnlessGranted('edit', $part); $this->denyAccessUnlessGranted('@info_providers.create_parts'); @@ -273,10 +329,22 @@ public function updateFromInfoProvider(Part $part, Request $request, string $pro $this->addFlash('notice', t('part.merge.flash.please_review')); + // Check if this is part of a bulk import job + $jobId = $request->query->get('jobId'); + $bulkJob = null; + if ($jobId) { + $bulkJob = $this->em->getRepository(\App\Entity\InfoProviderSystem\BulkInfoProviderImportJob::class)->find($jobId); + // Verify user owns this job + if ($bulkJob && $bulkJob->getCreatedBy() !== $this->getUser()) { + $bulkJob = null; + } + } + return $this->renderPartForm('update_from_ip', $request, $part, [ 'info_provider_dto' => $dto, ], [ - 'tname_before' => $old_name + 'tname_before' => $old_name, + 'bulk_job' => $bulkJob ]); } @@ -311,7 +379,7 @@ private function renderPartForm(string $mode, Request $request, Part $data, arra } catch (AttachmentDownloadException $attachmentDownloadException) { $this->addFlash( 'error', - $this->translator->trans('attachment.download_failed').' '.$attachmentDownloadException->getMessage() + $this->translator->trans('attachment.download_failed') . ' ' . $attachmentDownloadException->getMessage() ); } } @@ -352,6 +420,12 @@ private function renderPartForm(string $mode, Request $request, Part $data, arra return $this->redirectToRoute('part_new'); } + // Check if we're in bulk import mode and preserve jobId + $jobId = $request->query->get('jobId'); + if ($jobId && isset($merge_infos['bulk_job'])) { + return $this->redirectToRoute('part_edit', ['id' => $new_part->getID(), 'jobId' => $jobId]); + } + return $this->redirectToRoute('part_edit', ['id' => $new_part->getID()]); } @@ -370,13 +444,17 @@ private function renderPartForm(string $mode, Request $request, Part $data, arra $template = 'parts/edit/update_from_ip.html.twig'; } - return $this->render($template, + return $this->render( + $template, [ 'part' => $new_part, 'form' => $form, 'merge_old_name' => $merge_infos['tname_before'] ?? null, - 'merge_other' => $merge_infos['other_part'] ?? null - ]); + 'merge_other' => $merge_infos['other_part'] ?? null, + 'bulk_job' => $merge_infos['bulk_job'] ?? null, + 'jobId' => $request->query->get('jobId') + ] + ); } @@ -386,17 +464,17 @@ public function withdrawAddHandler(Part $part, Request $request, EntityManagerIn if ($this->isCsrfTokenValid('part_withraw' . $part->getID(), $request->request->get('_csfr'))) { //Retrieve partlot from the request $partLot = $em->find(PartLot::class, $request->request->get('lot_id')); - if(!$partLot instanceof PartLot) { + if (!$partLot instanceof PartLot) { throw new \RuntimeException('Part lot not found!'); } //Ensure that the partlot belongs to the part - if($partLot->getPart() !== $part) { + if ($partLot->getPart() !== $part) { throw new \RuntimeException("The origin partlot does not belong to the part!"); } //Try to determine the target lot (used for move actions), if the parameter is existing $targetId = $request->request->get('target_id', null); - $targetLot = $targetId ? $em->find(PartLot::class, $targetId) : null; + $targetLot = $targetId ? $em->find(PartLot::class, $targetId) : null; if ($targetLot && $targetLot->getPart() !== $part) { throw new \RuntimeException("The target partlot does not belong to the part!"); } @@ -410,12 +488,12 @@ public function withdrawAddHandler(Part $part, Request $request, EntityManagerIn $timestamp = null; $timestamp_str = $request->request->getString('timestamp', ''); //Try to parse the timestamp - if($timestamp_str !== '') { + if ($timestamp_str !== '') { $timestamp = new DateTime($timestamp_str); } //Ensure that the timestamp is not in the future - if($timestamp !== null && $timestamp > new DateTime("+20min")) { + if ($timestamp !== null && $timestamp > new DateTime("+20min")) { throw new \LogicException("The timestamp must not be in the future!"); } @@ -459,7 +537,7 @@ public function withdrawAddHandler(Part $part, Request $request, EntityManagerIn err: //If a redirect was passed, then redirect there - if($request->request->get('_redirect')) { + if ($request->request->get('_redirect')) { return $this->redirect($request->request->get('_redirect')); } //Otherwise just redirect to the part page diff --git a/src/Controller/PartImportExportController.php b/src/Controller/PartImportExportController.php index 528ab3e3c..45f90d75a 100644 --- a/src/Controller/PartImportExportController.php +++ b/src/Controller/PartImportExportController.php @@ -112,8 +112,9 @@ public function exportParts(Request $request, EntityExporter $entityExporter): R $ids = $request->query->get('ids', ''); $parts = $this->partsTableActionHandler->idStringToArray($ids); - if ($parts === []) { - throw new \RuntimeException('No parts found!'); + if (count($parts) === 0) { + $this->addFlash('error', 'entity.export.flash.error.no_entities'); + return $this->redirectToRoute('homepage'); } //Ensure that we have access to the parts diff --git a/src/Controller/PartListsController.php b/src/Controller/PartListsController.php index fde2805df..808b0c5d8 100644 --- a/src/Controller/PartListsController.php +++ b/src/Controller/PartListsController.php @@ -29,12 +29,15 @@ use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; +use App\Entity\Parts\Part; use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Exceptions\InvalidRegexException; use App\Form\Filters\PartFilterType; use App\Services\Parts\PartsTableActionHandler; use App\Services\Trees\NodesListBuilder; +use App\Settings\BehaviorSettings\SidebarSettings; +use App\Settings\BehaviorSettings\TableSettings; use Doctrine\DBAL\Exception\DriverException; use Doctrine\ORM\EntityManagerInterface; use Omines\DataTablesBundle\DataTableFactory; @@ -43,12 +46,30 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Translation\TranslatableMessage; use Symfony\Contracts\Translation\TranslatorInterface; +use function Symfony\Component\Translation\t; + class PartListsController extends AbstractController { - public function __construct(private readonly EntityManagerInterface $entityManager, private readonly NodesListBuilder $nodesListBuilder, private readonly DataTableFactory $dataTableFactory, private readonly TranslatorInterface $translator) + public function __construct(private readonly EntityManagerInterface $entityManager, + private readonly NodesListBuilder $nodesListBuilder, + private readonly DataTableFactory $dataTableFactory, + private readonly TranslatorInterface $translator, + private readonly TableSettings $tableSettings, + private readonly SidebarSettings $sidebarSettings, + ) + { + } + + /** + * Gets the filter operator to use by default (INCLUDING_CHILDREN or =) + * @return string + */ + private function getFilterOperator(): string { + return $this->sidebarSettings->dataStructureNodesTableIncludeChildren ? 'INCLUDING_CHILDREN' : '='; } #[Route(path: '/table/action', name: 'table_action', methods: ['POST'])] @@ -71,13 +92,32 @@ public function tableAction(Request $request, PartsTableActionHandler $actionHan if (null === $action || null === $ids) { $this->addFlash('error', 'part.table.actions.no_params_given'); } else { + $errors = []; + $parts = $actionHandler->idStringToArray($ids); - $redirectResponse = $actionHandler->handleAction($action, $parts, $target ? (int) $target : null, $redirect); + $redirectResponse = $actionHandler->handleAction($action, $parts, $target ? (int) $target : null, $redirect, $errors); //Save changes $this->entityManager->flush(); - $this->addFlash('success', 'part.table.actions.success'); + if (count($errors) === 0) { + $this->addFlash('success', 'part.table.actions.success'); + } else { + $this->addFlash('error', t('part.table.actions.error', ['%count%' => count($errors)])); + //Create a flash message for each error + foreach ($errors as $error) { + /** @var Part $part */ + $part = $error['part']; + + $this->addFlash('error', + t('part.table.actions.error_detail', [ + '%part_name%' => $part->getName(), + '%part_id%' => $part->getID(), + '%message%' => $error['message'] + ]) + ); + } + } } //If the action handler returned a response, we use it, otherwise we redirect back to the previous page. @@ -125,14 +165,21 @@ protected function showListWithFilter(Request $request, string $template, ?calla $filter_changer($filter); } - $filterForm = $this->createForm(PartFilterType::class, $filter, ['method' => 'GET']); - if($form_changer !== null) { - $form_changer($filterForm); - } + //If we are in a post request for the tables, we only have to apply the filter form if the submit query param was set + //This saves us some time from creating this complicated term on simple list pages, where no special filter is applied + $filterForm = null; + if ($request->getMethod() !== 'POST' || $request->query->has('part_filter')) { + $filterForm = $this->createForm(PartFilterType::class, $filter, ['method' => 'GET']); + if ($form_changer !== null) { + $form_changer($filterForm); + } - $filterForm->handleRequest($formRequest); + $filterForm->handleRequest($formRequest); + } - $table = $this->dataTableFactory->createFromType(PartsDataTable::class, array_merge(['filter' => $filter], $additional_table_vars)) + $table = $this->dataTableFactory->createFromType(PartsDataTable::class, array_merge( + ['filter' => $filter], $additional_table_vars), + ['pageLength' => $this->tableSettings->fullDefaultPageSize, 'lengthMenu' => PartsDataTable::LENGTH_MENU]) ->handleRequest($request); if ($table->isCallback()) { @@ -155,7 +202,7 @@ protected function showListWithFilter(Request $request, string $template, ?calla return $this->render($template, array_merge([ 'datatable' => $table, - 'filterForm' => $filterForm->createView(), + 'filterForm' => $filterForm?->createView(), ], $additonal_template_vars)); } @@ -167,7 +214,7 @@ public function showCategory(Category $category, Request $request): Response return $this->showListWithFilter($request, 'parts/lists/category_list.html.twig', function (PartFilter $filter) use ($category) { - $filter->category->setOperator('INCLUDING_CHILDREN')->setValue($category); + $filter->category->setOperator($this->getFilterOperator())->setValue($category); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('category')->get('value')); }, [ @@ -185,7 +232,7 @@ public function showFootprint(Footprint $footprint, Request $request): Response return $this->showListWithFilter($request, 'parts/lists/footprint_list.html.twig', function (PartFilter $filter) use ($footprint) { - $filter->footprint->setOperator('INCLUDING_CHILDREN')->setValue($footprint); + $filter->footprint->setOperator($this->getFilterOperator())->setValue($footprint); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('footprint')->get('value')); }, [ @@ -203,7 +250,7 @@ public function showManufacturer(Manufacturer $manufacturer, Request $request): return $this->showListWithFilter($request, 'parts/lists/manufacturer_list.html.twig', function (PartFilter $filter) use ($manufacturer) { - $filter->manufacturer->setOperator('INCLUDING_CHILDREN')->setValue($manufacturer); + $filter->manufacturer->setOperator($this->getFilterOperator())->setValue($manufacturer); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('manufacturer')->get('value')); }, [ @@ -221,7 +268,7 @@ public function showStorelocation(StorageLocation $storelocation, Request $reque return $this->showListWithFilter($request, 'parts/lists/store_location_list.html.twig', function (PartFilter $filter) use ($storelocation) { - $filter->storelocation->setOperator('INCLUDING_CHILDREN')->setValue($storelocation); + $filter->storelocation->setOperator($this->getFilterOperator())->setValue($storelocation); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('storelocation')->get('value')); }, [ @@ -239,7 +286,7 @@ public function showSupplier(Supplier $supplier, Request $request): Response return $this->showListWithFilter($request, 'parts/lists/supplier_list.html.twig', function (PartFilter $filter) use ($supplier) { - $filter->supplier->setOperator('INCLUDING_CHILDREN')->setValue($supplier); + $filter->supplier->setOperator($this->getFilterOperator())->setValue($supplier); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('supplier')->get('value')); }, [ diff --git a/src/Controller/ProjectController.php b/src/Controller/ProjectController.php index 761e498c1..2a6d19ee2 100644 --- a/src/Controller/ProjectController.php +++ b/src/Controller/ProjectController.php @@ -31,10 +31,12 @@ use App\Helpers\Projects\ProjectBuildRequest; use App\Services\ImportExportSystem\BOMImporter; use App\Services\ProjectSystem\ProjectBuildHelper; +use App\Settings\BehaviorSettings\TableSettings; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManagerInterface; use League\Csv\SyntaxError; use Omines\DataTablesBundle\DataTableFactory; +use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; @@ -55,11 +57,12 @@ public function __construct(private readonly DataTableFactory $dataTableFactory) } #[Route(path: '/{id}/info', name: 'project_info', requirements: ['id' => '\d+'])] - public function info(Project $project, Request $request, ProjectBuildHelper $buildHelper): Response + public function info(Project $project, Request $request, ProjectBuildHelper $buildHelper, TableSettings $tableSettings): Response { $this->denyAccessUnlessGranted('read', $project); - $table = $this->dataTableFactory->createFromType(ProjectBomEntriesDataTable::class, ['project' => $project]) + $table = $this->dataTableFactory->createFromType(ProjectBomEntriesDataTable::class, ['project' => $project], + ['pageLength' => $tableSettings->fullDefaultPageSize]) ->handleRequest($request); if ($table->isCallback()) { @@ -100,9 +103,14 @@ public function build(Project $project, Request $request, ProjectBuildHelper $bu $this->addFlash('success', 'project.build.flash.success'); return $this->redirect( - $request->get('_redirect', - $this->generateUrl('project_info', ['id' => $project->getID()] - ))); + $request->get( + '_redirect', + $this->generateUrl( + 'project_info', + ['id' => $project->getID()] + ) + ) + ); } $this->addFlash('error', 'project.build.flash.invalid_input'); @@ -118,9 +126,13 @@ public function build(Project $project, Request $request, ProjectBuildHelper $bu } #[Route(path: '/{id}/import_bom', name: 'project_import_bom', requirements: ['id' => '\d+'])] - public function importBOM(Request $request, EntityManagerInterface $entityManager, Project $project, - BOMImporter $BOMImporter, ValidatorInterface $validator): Response - { + public function importBOM( + Request $request, + EntityManagerInterface $entityManager, + Project $project, + BOMImporter $BOMImporter, + ValidatorInterface $validator + ): Response { $this->denyAccessUnlessGranted('edit', $project); $builder = $this->createFormBuilder(); @@ -136,6 +148,8 @@ public function importBOM(Request $request, EntityManagerInterface $entityManage 'required' => true, 'choices' => [ 'project.bom_import.type.kicad_pcbnew' => 'kicad_pcbnew', + 'project.bom_import.type.kicad_schematic' => 'kicad_schematic', + 'project.bom_import.type.generic_csv' => 'generic_csv', ] ]); $builder->add('clear_existing_bom', CheckboxType::class, [ @@ -159,25 +173,40 @@ public function importBOM(Request $request, EntityManagerInterface $entityManage $entityManager->flush(); } + $import_type = $form->get('type')->getData(); + try { + // For schematic imports, redirect to field mapping step + if (in_array($import_type, ['kicad_schematic', 'generic_csv'], true)) { + // Store file content and options in session for field mapping step + $file_content = $form->get('file')->getData()->getContent(); + $clear_existing = $form->get('clear_existing_bom')->getData(); + + $request->getSession()->set('bom_import_data', $file_content); + $request->getSession()->set('bom_import_clear', $clear_existing); + + return $this->redirectToRoute('project_import_bom_map_fields', ['id' => $project->getID()]); + } + + // For PCB imports, proceed directly $entries = $BOMImporter->importFileIntoProject($form->get('file')->getData(), $project, [ - 'type' => $form->get('type')->getData(), + 'type' => $import_type, ]); - //Validate the project entries + // Validate the project entries $errors = $validator->validateProperty($project, 'bom_entries'); - //If no validation errors occured, save the changes and redirect to edit page - if (count ($errors) === 0) { + // If no validation errors occurred, save the changes and redirect to edit page + if (count($errors) === 0) { $this->addFlash('success', t('project.bom_import.flash.success', ['%count%' => count($entries)])); $entityManager->flush(); return $this->redirectToRoute('project_edit', ['id' => $project->getID()]); } - //When we get here, there were validation errors + // When we get here, there were validation errors $this->addFlash('error', t('project.bom_import.flash.invalid_entries')); - } catch (\UnexpectedValueException|SyntaxError $e) { + } catch (\UnexpectedValueException | SyntaxError $e) { $this->addFlash('error', t('project.bom_import.flash.invalid_file', ['%message%' => $e->getMessage()])); } } @@ -189,11 +218,267 @@ public function importBOM(Request $request, EntityManagerInterface $entityManage ]); } + #[Route(path: '/{id}/import_bom/map_fields', name: 'project_import_bom_map_fields', requirements: ['id' => '\d+'])] + public function importBOMMapFields( + Request $request, + EntityManagerInterface $entityManager, + Project $project, + BOMImporter $BOMImporter, + ValidatorInterface $validator, + LoggerInterface $logger + ): Response { + $this->denyAccessUnlessGranted('edit', $project); + + // Get stored data from session + $file_content = $request->getSession()->get('bom_import_data'); + $clear_existing = $request->getSession()->get('bom_import_clear', false); + + + if (!$file_content) { + $this->addFlash('error', 'project.bom_import.flash.session_expired'); + return $this->redirectToRoute('project_import_bom', ['id' => $project->getID()]); + } + + // Detect fields and get suggestions + $detected_fields = $BOMImporter->detectFields($file_content); + $suggested_mapping = $BOMImporter->getSuggestedFieldMapping($detected_fields); + + // Create mapping of original field names to sanitized field names for template + $field_name_mapping = []; + foreach ($detected_fields as $field) { + $sanitized_field = preg_replace('/[^a-zA-Z0-9_-]/', '_', $field); + $field_name_mapping[$field] = $sanitized_field; + } + + // Create form for field mapping + $builder = $this->createFormBuilder(); + + // Add delimiter selection + $builder->add('delimiter', ChoiceType::class, [ + 'label' => 'project.bom_import.delimiter', + 'required' => true, + 'data' => ',', + 'choices' => [ + 'project.bom_import.delimiter.comma' => ',', + 'project.bom_import.delimiter.semicolon' => ';', + 'project.bom_import.delimiter.tab' => "\t", + ] + ]); + + // Get dynamic field mapping targets from BOMImporter + $available_targets = $BOMImporter->getAvailableFieldTargets(); + $target_fields = ['project.bom_import.field_mapping.ignore' => '']; + + foreach ($available_targets as $target_key => $target_info) { + $target_fields[$target_info['label']] = $target_key; + } + + foreach ($detected_fields as $field) { + // Sanitize field name for form use - replace invalid characters with underscores + $sanitized_field = preg_replace('/[^a-zA-Z0-9_-]/', '_', $field); + $builder->add('mapping_' . $sanitized_field, ChoiceType::class, [ + 'label' => $field, + 'required' => false, + 'choices' => $target_fields, + 'data' => $suggested_mapping[$field] ?? '', + ]); + } + + $builder->add('submit', SubmitType::class, [ + 'label' => 'project.bom_import.preview', + ]); + + $form = $builder->getForm(); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + // Build field mapping array with priority support + $field_mapping = []; + $field_priorities = []; + $delimiter = $form->get('delimiter')->getData(); + + foreach ($detected_fields as $field) { + $sanitized_field = preg_replace('/[^a-zA-Z0-9_-]/', '_', $field); + $target = $form->get('mapping_' . $sanitized_field)->getData(); + if (!empty($target)) { + $field_mapping[$field] = $target; + + // Get priority from request (default to 10) + $priority = $request->request->get('priority_' . $sanitized_field, 10); + $field_priorities[$field] = (int) $priority; + } + } + + // Validate field mapping + $validation = $BOMImporter->validateFieldMapping($field_mapping, $detected_fields); + + if (!$validation['is_valid']) { + foreach ($validation['errors'] as $error) { + $this->addFlash('error', $error); + } + foreach ($validation['warnings'] as $warning) { + $this->addFlash('warning', $warning); + } + + return $this->render('projects/import_bom_map_fields.html.twig', [ + 'project' => $project, + 'form' => $form->createView(), + 'detected_fields' => $detected_fields, + 'suggested_mapping' => $suggested_mapping, + 'field_name_mapping' => $field_name_mapping, + ]); + } + + // Show warnings but continue + foreach ($validation['warnings'] as $warning) { + $this->addFlash('warning', $warning); + } + + try { + // Re-detect fields with chosen delimiter + $detected_fields = $BOMImporter->detectFields($file_content, $delimiter); + + // Clear existing BOM entries if requested + if ($clear_existing) { + $existing_count = $project->getBomEntries()->count(); + $logger->info('Clearing existing BOM entries', [ + 'existing_count' => $existing_count, + 'project_id' => $project->getID(), + ]); + $project->getBomEntries()->clear(); + $entityManager->flush(); + $logger->info('Existing BOM entries cleared'); + } else { + $existing_count = $project->getBomEntries()->count(); + $logger->info('Keeping existing BOM entries', [ + 'existing_count' => $existing_count, + 'project_id' => $project->getID(), + ]); + } + + // Validate data before importing + $validation_result = $BOMImporter->validateBOMData($file_content, [ + 'type' => 'kicad_schematic', + 'field_mapping' => $field_mapping, + 'field_priorities' => $field_priorities, + 'delimiter' => $delimiter, + ]); + + // Log validation results + $logger->info('BOM import validation completed', [ + 'total_entries' => $validation_result['total_entries'], + 'valid_entries' => $validation_result['valid_entries'], + 'invalid_entries' => $validation_result['invalid_entries'], + 'error_count' => count($validation_result['errors']), + 'warning_count' => count($validation_result['warnings']), + ]); + + // Show validation warnings to user + foreach ($validation_result['warnings'] as $warning) { + $this->addFlash('warning', $warning); + } + + // If there are validation errors, show them and stop + if (!empty($validation_result['errors'])) { + foreach ($validation_result['errors'] as $error) { + $this->addFlash('error', $error); + } + + return $this->render('projects/import_bom_map_fields.html.twig', [ + 'project' => $project, + 'form' => $form->createView(), + 'detected_fields' => $detected_fields, + 'suggested_mapping' => $suggested_mapping, + 'field_name_mapping' => $field_name_mapping, + 'validation_result' => $validation_result, + ]); + } + + // Import with field mapping and priorities (validation already passed) + $entries = $BOMImporter->stringToBOMEntries($file_content, [ + 'type' => 'kicad_schematic', + 'field_mapping' => $field_mapping, + 'field_priorities' => $field_priorities, + 'delimiter' => $delimiter, + ]); + + // Log entry details for debugging + $logger->info('BOM entries created', [ + 'total_entries' => count($entries), + ]); + + foreach ($entries as $index => $entry) { + $logger->debug("BOM entry {$index}", [ + 'name' => $entry->getName(), + 'mountnames' => $entry->getMountnames(), + 'quantity' => $entry->getQuantity(), + 'comment' => $entry->getComment(), + 'part_id' => $entry->getPart()?->getID(), + ]); + } + + // Assign entries to project + $logger->info('Adding BOM entries to project', [ + 'entries_count' => count($entries), + 'project_id' => $project->getID(), + ]); + + foreach ($entries as $index => $entry) { + $logger->debug("Adding BOM entry {$index} to project", [ + 'name' => $entry->getName(), + 'part_id' => $entry->getPart()?->getID(), + 'quantity' => $entry->getQuantity(), + ]); + $project->addBomEntry($entry); + } + + // Validate the project entries (includes collection constraints) + $errors = $validator->validateProperty($project, 'bom_entries'); + + // If no validation errors occurred, save and redirect + if (count($errors) === 0) { + $this->addFlash('success', t('project.bom_import.flash.success', ['%count%' => count($entries)])); + $entityManager->flush(); + + // Clear session data + $request->getSession()->remove('bom_import_data'); + $request->getSession()->remove('bom_import_clear'); + + return $this->redirectToRoute('project_edit', ['id' => $project->getID()]); + } + + // When we get here, there were validation errors + $this->addFlash('error', t('project.bom_import.flash.invalid_entries')); + + //Print validation errors to log for debugging + foreach ($errors as $error) { + $logger->error('BOM entry validation error', [ + 'message' => $error->getMessage(), + 'invalid_value' => $error->getInvalidValue(), + ]); + //And show as flash message + $this->addFlash('error', $error->getMessage(),); + } + + } catch (\UnexpectedValueException | SyntaxError $e) { + $this->addFlash('error', t('project.bom_import.flash.invalid_file', ['%message%' => $e->getMessage()])); + } + } + + return $this->render('projects/import_bom_map_fields.html.twig', [ + 'project' => $project, + 'form' => $form, + 'detected_fields' => $detected_fields, + 'suggested_mapping' => $suggested_mapping, + 'field_name_mapping' => $field_name_mapping, + ]); + } + #[Route(path: '/add_parts', name: 'project_add_parts_no_id')] #[Route(path: '/{id}/add_parts', name: 'project_add_parts', requirements: ['id' => '\d+'])] public function addPart(Request $request, EntityManagerInterface $entityManager, ?Project $project): Response { - if($project instanceof Project) { + if ($project instanceof Project) { $this->denyAccessUnlessGranted('edit', $project); } else { $this->denyAccessUnlessGranted('@projects.edit'); @@ -240,7 +525,7 @@ public function addPart(Request $request, EntityManagerInterface $entityManager, $data = $form->getData(); $bom_entries = $data['bom_entries']; - foreach ($bom_entries as $bom_entry){ + foreach ($bom_entries as $bom_entry) { $target_project->addBOMEntry($bom_entry); } diff --git a/src/Controller/RedirectController.php b/src/Controller/RedirectController.php index 65bd78f50..a4cac3aaf 100644 --- a/src/Controller/RedirectController.php +++ b/src/Controller/RedirectController.php @@ -23,6 +23,7 @@ namespace App\Controller; use App\Entity\UserSystem\User; +use App\Settings\SystemSettings\LocalizationSettings; use function function_exists; use function in_array; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -35,7 +36,7 @@ */ class RedirectController extends AbstractController { - public function __construct(protected string $default_locale, protected TranslatorInterface $translator, protected bool $enforce_index_php) + public function __construct(private readonly LocalizationSettings $localizationSettings, protected TranslatorInterface $translator, protected bool $enforce_index_php) { } @@ -46,7 +47,7 @@ public function __construct(protected string $default_locale, protected Translat public function addLocalePart(Request $request): RedirectResponse { //By default, we use the global default locale - $locale = $this->default_locale; + $locale = $this->localizationSettings->locale; //Check if a user has set a preferred language setting: $user = $this->getUser(); diff --git a/src/Controller/SelectAPIController.php b/src/Controller/SelectAPIController.php index 500621182..c1e682c88 100644 --- a/src/Controller/SelectAPIController.php +++ b/src/Controller/SelectAPIController.php @@ -29,6 +29,7 @@ use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; +use App\Entity\Parts\StorageLocation; use App\Entity\ProjectSystem\Project; use App\Form\Type\Helper\StructuralEntityChoiceHelper; use App\Services\Trees\NodesListBuilder; @@ -78,6 +79,12 @@ public function projects(): Response return $this->getResponseForClass(Project::class, false); } + #[Route(path: '/storage_location', name: 'select_storage_location')] + public function locations(): Response + { + return $this->getResponseForClass(StorageLocation::class, true); + } + #[Route(path: '/export_level', name: 'select_export_level')] public function exportLevel(): Response { diff --git a/src/Controller/SettingsController.php b/src/Controller/SettingsController.php new file mode 100644 index 000000000..3479cf849 --- /dev/null +++ b/src/Controller/SettingsController.php @@ -0,0 +1,81 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Controller; + +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use App\Settings\AppSettings; +use Jbtronics\SettingsBundle\Form\SettingsFormFactoryInterface; +use Jbtronics\SettingsBundle\Manager\SettingsManagerInterface; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Attribute\Route; +use Symfony\Contracts\Cache\TagAwareCacheInterface; + +use function Symfony\Component\Translation\t; + +class SettingsController extends AbstractController +{ + public function __construct(private readonly SettingsManagerInterface $settingsManager, private readonly SettingsFormFactoryInterface $settingsFormFactory) + {} + + #[Route("/settings", name: "system_settings")] + public function systemSettings(Request $request, TagAwareCacheInterface $cache): Response + { + $this->denyAccessUnlessGranted('@config.change_system_settings'); + + //Create a clone of the settings object + $settings = $this->settingsManager->createTemporaryCopy(AppSettings::class); + + //Create a form builder for the settings object + $builder = $this->settingsFormFactory->createSettingsFormBuilder($settings); + + //Add a submit button to the form + $builder->add('submit', SubmitType::class, ['label' => 'save']); + + //Create the form + $form = $builder->getForm(); + $form->handleRequest($request); + + //If the form was submitted and is valid, save the settings + if ($form->isSubmitted() && $form->isValid()) { + $this->settingsManager->mergeTemporaryCopy($settings); + $this->settingsManager->save($settings); + + //It might be possible, that the tree settings have changed, so clear the cache + $cache->invalidateTags(['tree_treeview', 'sidebar_tree_update']); + + $this->addFlash('success', t('settings.flash.saved')); + } + + if ($form->isSubmitted() && !$form->isValid()) { + $this->addFlash('error', t('settings.flash.invalid')); + } + + //Render the form + return $this->render('settings/settings.html.twig', [ + 'form' => $form + ]); + } +} diff --git a/src/Controller/ToolsController.php b/src/Controller/ToolsController.php index dbcb91a1a..d78aff620 100644 --- a/src/Controller/ToolsController.php +++ b/src/Controller/ToolsController.php @@ -29,6 +29,7 @@ use App\Services\Doctrine\NatsortDebugHelper; use App\Services\Misc\GitVersionInfo; use App\Services\System\UpdateAvailableManager; +use App\Settings\AppSettings; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; @@ -47,7 +48,8 @@ public function reelCalculator(): Response #[Route(path: '/server_infos', name: 'tools_server_infos')] public function systemInfos(GitVersionInfo $versionInfo, DBInfoHelper $DBInfoHelper, NatsortDebugHelper $natsortDebugHelper, - AttachmentSubmitHandler $attachmentSubmitHandler, UpdateAvailableManager $updateAvailableManager): Response + AttachmentSubmitHandler $attachmentSubmitHandler, UpdateAvailableManager $updateAvailableManager, + AppSettings $settings): Response { $this->denyAccessUnlessGranted('@system.server_infos'); @@ -55,23 +57,23 @@ public function systemInfos(GitVersionInfo $versionInfo, DBInfoHelper $DBInfoHel //Part-DB section 'git_branch' => $versionInfo->getGitBranchName(), 'git_commit' => $versionInfo->getGitCommitHash(), - 'default_locale' => $this->getParameter('partdb.locale'), - 'default_timezone' => $this->getParameter('partdb.timezone'), - 'default_currency' => $this->getParameter('partdb.default_currency'), - 'default_theme' => $this->getParameter('partdb.global_theme'), + 'default_locale' => $settings->system->localization->locale, + 'default_timezone' => $settings->system->localization->timezone, + 'default_currency' => $settings->system->localization->baseCurrency, + 'default_theme' => $settings->system->customization->theme, 'enabled_locales' => $this->getParameter('partdb.locale_menu'), 'demo_mode' => $this->getParameter('partdb.demo_mode'), + 'use_gravatar' => $settings->system->privacy->useGravatar, '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'), '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'), - 'allow_attachments_downloads' => $this->getParameter('partdb.attachments.allow_downloads'), + 'allow_attachments_downloads' => $settings->system->attachments->allowDownloads, 'detailed_error_pages' => $this->getParameter('partdb.error_pages.show_help'), 'error_page_admin_email' => $this->getParameter('partdb.error_pages.admin_email'), - 'configured_max_file_size' => $this->getParameter('partdb.attachments.max_file_size'), + 'configured_max_file_size' => $settings->system->attachments->maxFileSize, 'effective_max_file_size' => $attachmentSubmitHandler->getMaximumAllowedUploadSize(), 'saml_enabled' => $this->getParameter('partdb.saml.enabled'), diff --git a/src/DataFixtures/CurrencyFixtures.php b/src/DataFixtures/CurrencyFixtures.php new file mode 100644 index 000000000..2de5b277d --- /dev/null +++ b/src/DataFixtures/CurrencyFixtures.php @@ -0,0 +1,64 @@ +. + */ + +declare(strict_types=1); + + +namespace App\DataFixtures; + +use App\Entity\PriceInformations\Currency; +use Brick\Math\BigDecimal; +use Doctrine\Bundle\FixturesBundle\Fixture; +use Doctrine\Persistence\ObjectManager; + +class CurrencyFixtures extends Fixture +{ + public function load(ObjectManager $manager): void + { + $currency1 = new Currency(); + $currency1->setName('US-Dollar'); + $currency1->setIsoCode('USD'); + $manager->persist($currency1); + + $currency2 = new Currency(); + $currency2->setName('Swiss Franc'); + $currency2->setIsoCode('CHF'); + $currency2->setExchangeRate(BigDecimal::of('0.91')); + $manager->persist($currency2); + + $currency3 = new Currency(); + $currency3->setName('Great British Pound'); + $currency3->setIsoCode('GBP'); + $currency3->setExchangeRate(BigDecimal::of('0.78')); + $manager->persist($currency3); + + $currency7 = new Currency(); + $currency7->setName('Test Currency with long name'); + $currency7->setIsoCode('CNY'); + $manager->persist($currency7); + + $manager->flush(); + + + //Ensure that currency 7 gets ID 7 + $manager->getRepository(Currency::class)->changeID($currency7, 7); + $manager->flush(); + } +} diff --git a/src/DataFixtures/DataStructureFixtures.php b/src/DataFixtures/DataStructureFixtures.php index fc713d4df..9c6853384 100644 --- a/src/DataFixtures/DataStructureFixtures.php +++ b/src/DataFixtures/DataStructureFixtures.php @@ -24,6 +24,7 @@ use App\Entity\Attachments\AttachmentType; use App\Entity\Base\AbstractStructuralDBElement; +use App\Entity\Parts\PartCustomState; use App\Entity\ProjectSystem\Project; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; @@ -50,7 +51,7 @@ public function load(ObjectManager $manager): void { //Reset autoincrement $types = [AttachmentType::class, Project::class, Category::class, Footprint::class, Manufacturer::class, - MeasurementUnit::class, StorageLocation::class, Supplier::class,]; + MeasurementUnit::class, StorageLocation::class, Supplier::class, PartCustomState::class]; foreach ($types as $type) { $this->createNodesForClass($type, $manager); diff --git a/src/DataFixtures/PartFixtures.php b/src/DataFixtures/PartFixtures.php index 0c8ea36d9..a60d037dc 100644 --- a/src/DataFixtures/PartFixtures.php +++ b/src/DataFixtures/PartFixtures.php @@ -131,7 +131,7 @@ public function load(ObjectManager $manager): void $attachment = new PartAttachment(); $attachment->setName('Test2'); - $attachment->setPath('invalid'); + $attachment->setInternalPath('invalid'); $attachment->setShowInTable(true); $attachment->setAttachmentType($manager->find(AttachmentType::class, 1)); $part->addAttachment($attachment); diff --git a/src/DataTables/Adapters/TwoStepORMAdapter.php b/src/DataTables/Adapters/TwoStepORMAdapter.php index 2086c9073..51315c32a 100644 --- a/src/DataTables/Adapters/TwoStepORMAdapter.php +++ b/src/DataTables/Adapters/TwoStepORMAdapter.php @@ -54,7 +54,7 @@ class TwoStepORMAdapter extends ORMAdapter private \Closure|null $query_modifier = null; - public function __construct(ManagerRegistry $registry = null) + public function __construct(?ManagerRegistry $registry = null) { parent::__construct($registry); $this->detailQueryCallable = static function (QueryBuilder $qb, array $ids): never { diff --git a/src/DataTables/AttachmentDataTable.php b/src/DataTables/AttachmentDataTable.php index 0d6c5b539..16e6a7a7d 100644 --- a/src/DataTables/AttachmentDataTable.php +++ b/src/DataTables/AttachmentDataTable.php @@ -50,8 +50,8 @@ public function configure(DataTable $dataTable, array $options): void { $dataTable->add('dont_matter', RowClassColumn::class, [ 'render' => function ($value, Attachment $context): string { - //Mark attachments with missing files yellow - if(!$this->attachmentHelper->isFileExisting($context)){ + //Mark attachments yellow which have an internal file linked that doesn't exist + if($context->hasInternal() && !$this->attachmentHelper->isInternalFileExisting($context)){ return 'table-warning'; } @@ -64,8 +64,8 @@ public function configure(DataTable $dataTable, array $options): void 'className' => 'no-colvis', 'render' => function ($value, Attachment $context): string { if ($context->isPicture() - && !$context->isExternal() - && $this->attachmentHelper->isFileExisting($context)) { + && $this->attachmentHelper->isInternalFileExisting($context)) { + $title = htmlspecialchars($context->getName()); if ($context->getFilename()) { $title .= ' ('.htmlspecialchars($context->getFilename()).')'; @@ -93,26 +93,6 @@ public function configure(DataTable $dataTable, array $options): void $dataTable->add('name', TextColumn::class, [ 'label' => 'attachment.edit.name', 'orderField' => 'NATSORT(attachment.name)', - 'render' => function ($value, Attachment $context) { - //Link to external source - if ($context->isExternal()) { - return sprintf( - '%s', - htmlspecialchars((string) $context->getURL()), - htmlspecialchars($value) - ); - } - - if ($this->attachmentHelper->isFileExisting($context)) { - return sprintf( - '%s', - $this->entityURLGenerator->viewURL($context), - htmlspecialchars($value) - ); - } - - return $value; - }, ]); $dataTable->add('attachment_type', TextColumn::class, [ @@ -136,25 +116,60 @@ public function configure(DataTable $dataTable, array $options): void ), ]); - $dataTable->add('filename', TextColumn::class, [ - 'label' => $this->translator->trans('attachment.table.filename'), + $dataTable->add('internal_link', TextColumn::class, [ + 'label' => 'attachment.table.internal_file', 'propertyPath' => 'filename', + 'orderField' => 'NATSORT(attachment.original_filename)', + 'render' => function ($value, Attachment $context) { + if ($this->attachmentHelper->isInternalFileExisting($context)) { + return sprintf( + '%s', + $this->entityURLGenerator->viewURL($context), + htmlspecialchars($value) + ); + } + + return $value; + } + ]); + + $dataTable->add('external_link', TextColumn::class, [ + 'label' => 'attachment.table.external_link', + 'propertyPath' => 'host', + 'orderField' => 'attachment.external_path', + 'render' => function ($value, Attachment $context) { + if ($context->hasExternal()) { + return sprintf( + '%s', + htmlspecialchars((string) $context->getExternalPath()), + htmlspecialchars((string) $context->getExternalPath()), + htmlspecialchars($value), + ); + } + + return $value; + } ]); $dataTable->add('filesize', TextColumn::class, [ 'label' => $this->translator->trans('attachment.table.filesize'), 'render' => function ($value, Attachment $context) { - if ($context->isExternal()) { + if (!$context->hasInternal()) { return sprintf( ' %s ', - $this->translator->trans('attachment.external') + $this->translator->trans('attachment.external_only') ); } - if ($this->attachmentHelper->isFileExisting($context)) { - return $this->attachmentHelper->getHumanFileSize($context); + if ($this->attachmentHelper->isInternalFileExisting($context)) { + return sprintf( + ' + %s + ', + $this->attachmentHelper->getHumanFileSize($context) + ); } return sprintf( diff --git a/src/DataTables/Filters/AttachmentFilter.php b/src/DataTables/Filters/AttachmentFilter.php index 9f8cf0945..69d2aeaca 100644 --- a/src/DataTables/Filters/AttachmentFilter.php +++ b/src/DataTables/Filters/AttachmentFilter.php @@ -22,6 +22,7 @@ */ namespace App\DataTables\Filters; +use App\DataTables\Filters\Constraints\AbstractConstraint; use App\DataTables\Filters\Constraints\BooleanConstraint; use App\DataTables\Filters\Constraints\DateTimeConstraint; use App\DataTables\Filters\Constraints\EntityConstraint; @@ -32,6 +33,7 @@ use App\Entity\Attachments\AttachmentType; use App\Services\Trees\NodesListBuilder; use Doctrine\ORM\QueryBuilder; +use Omines\DataTablesBundle\Filter\AbstractFilter; class AttachmentFilter implements FilterInterface { @@ -45,9 +47,15 @@ class AttachmentFilter implements FilterInterface public readonly DateTimeConstraint $lastModified; public readonly DateTimeConstraint $addedDate; + public readonly TextConstraint $originalFileName; + public readonly TextConstraint $externalLink; + public function __construct(NodesListBuilder $nodesListBuilder) { + //Must be done for every new set of attachment filters, to ensure deterministic parameter names. + AbstractConstraint::resetParameterCounter(); + $this->dbId = new IntConstraint('attachment.id'); $this->name = new TextConstraint('attachment.name'); $this->targetType = new InstanceOfConstraint('attachment'); @@ -55,6 +63,9 @@ public function __construct(NodesListBuilder $nodesListBuilder) $this->lastModified = new DateTimeConstraint('attachment.lastModified'); $this->addedDate = new DateTimeConstraint('attachment.addedDate'); $this->showInTable = new BooleanConstraint('attachment.show_in_table'); + $this->originalFileName = new TextConstraint('attachment.original_filename'); + $this->externalLink = new TextConstraint('attachment.external_path'); + } public function apply(QueryBuilder $queryBuilder): void diff --git a/src/DataTables/Filters/Constraints/AbstractConstraint.php b/src/DataTables/Filters/Constraints/AbstractConstraint.php index cbb62352e..c632b2a4f 100644 --- a/src/DataTables/Filters/Constraints/AbstractConstraint.php +++ b/src/DataTables/Filters/Constraints/AbstractConstraint.php @@ -28,10 +28,7 @@ abstract class AbstractConstraint implements FilterInterface { use FilterTrait; - /** - * @var string - */ - protected string $identifier; + protected ?string $identifier; /** @@ -45,7 +42,7 @@ public function __construct( * @var string The property where this BooleanConstraint should apply to */ protected string $property, - string $identifier = null) + ?string $identifier = null) { $this->identifier = $identifier ?? $this->generateParameterIdentifier($property); } diff --git a/src/DataTables/Filters/Constraints/BooleanConstraint.php b/src/DataTables/Filters/Constraints/BooleanConstraint.php index b3f1dc471..8eb4f0421 100644 --- a/src/DataTables/Filters/Constraints/BooleanConstraint.php +++ b/src/DataTables/Filters/Constraints/BooleanConstraint.php @@ -28,7 +28,7 @@ class BooleanConstraint extends AbstractConstraint { public function __construct( string $property, - string $identifier = null, + ?string $identifier = null, /** @var bool|null The value of our constraint */ protected ?bool $value = null ) diff --git a/src/DataTables/Filters/Constraints/DateTimeConstraint.php b/src/DataTables/Filters/Constraints/DateTimeConstraint.php index 23134de51..a30431705 100644 --- a/src/DataTables/Filters/Constraints/DateTimeConstraint.php +++ b/src/DataTables/Filters/Constraints/DateTimeConstraint.php @@ -34,7 +34,7 @@ class DateTimeConstraint extends AbstractConstraint public function __construct( string $property, - string $identifier = null, + ?string $identifier = null, /** * The value1 used for comparison (this is the main one used for all mono-value comparisons) */ diff --git a/src/DataTables/Filters/Constraints/EntityConstraint.php b/src/DataTables/Filters/Constraints/EntityConstraint.php index 6e9721f42..c75da80d4 100644 --- a/src/DataTables/Filters/Constraints/EntityConstraint.php +++ b/src/DataTables/Filters/Constraints/EntityConstraint.php @@ -46,7 +46,7 @@ class EntityConstraint extends AbstractConstraint public function __construct(protected ?NodesListBuilder $nodesListBuilder, protected string $class, string $property, - string $identifier = null, + ?string $identifier = null, protected ?AbstractDBElement $value = null, protected ?string $operator = null) { diff --git a/src/DataTables/Filters/Constraints/FilterTrait.php b/src/DataTables/Filters/Constraints/FilterTrait.php index 3260e4e3d..2932914ad 100644 --- a/src/DataTables/Filters/Constraints/FilterTrait.php +++ b/src/DataTables/Filters/Constraints/FilterTrait.php @@ -28,6 +28,7 @@ trait FilterTrait { protected bool $useHaving = false; + protected static int $parameterCounter = 0; public function useHaving($value = true): static { @@ -50,8 +51,18 @@ protected function generateParameterIdentifier(string $property): string { //Replace all special characters with underscores $property = preg_replace('/\W/', '_', $property); - //Add a random number to the end of the property name for uniqueness - return $property . '_' . uniqid("", false); + return $property . '_' . (self::$parameterCounter++) . '_'; + } + + /** + * Resets the parameter counter, so the next call to generateParameterIdentifier will start from 0 again. + * This should be done before initializing a new set of filters to a fresh query builder, to ensure that the parameter + * identifiers are deterministic so that they are cacheable. + * @return void + */ + public static function resetParameterCounter(): void + { + self::$parameterCounter = 0; } /** diff --git a/src/DataTables/Filters/Constraints/NumberConstraint.php b/src/DataTables/Filters/Constraints/NumberConstraint.php index c872dade4..dc7cf733d 100644 --- a/src/DataTables/Filters/Constraints/NumberConstraint.php +++ b/src/DataTables/Filters/Constraints/NumberConstraint.php @@ -31,7 +31,7 @@ class NumberConstraint extends AbstractConstraint public function __construct( string $property, - string $identifier = null, + ?string $identifier = null, /** * The value1 used for comparison (this is the main one used for all mono-value comparisons) */ diff --git a/src/DataTables/Filters/Constraints/Part/BulkImportJobExistsConstraint.php b/src/DataTables/Filters/Constraints/Part/BulkImportJobExistsConstraint.php new file mode 100644 index 000000000..9d21dd588 --- /dev/null +++ b/src/DataTables/Filters/Constraints/Part/BulkImportJobExistsConstraint.php @@ -0,0 +1,59 @@ +. + */ + +namespace App\DataTables\Filters\Constraints\Part; + +use App\DataTables\Filters\Constraints\BooleanConstraint; +use App\Entity\InfoProviderSystem\BulkInfoProviderImportJobPart; +use Doctrine\ORM\QueryBuilder; + +class BulkImportJobExistsConstraint extends BooleanConstraint +{ + + public function __construct() + { + parent::__construct('bulk_import_job_exists'); + } + + public function apply(QueryBuilder $queryBuilder): void + { + // Do not apply a filter if value is null (filter is set to ignore) + if (!$this->isEnabled()) { + return; + } + + // Use EXISTS subquery to avoid join conflicts + $existsSubquery = $queryBuilder->getEntityManager()->createQueryBuilder(); + $existsSubquery->select('1') + ->from(BulkInfoProviderImportJobPart::class, 'bip_exists') + ->where('bip_exists.part = part.id'); + + if ($this->value === true) { + // Filter for parts that ARE in bulk import jobs + $queryBuilder->andWhere('EXISTS (' . $existsSubquery->getDQL() . ')'); + } else { + // Filter for parts that are NOT in bulk import jobs + $queryBuilder->andWhere('NOT EXISTS (' . $existsSubquery->getDQL() . ')'); + } + } +} diff --git a/src/DataTables/Filters/Constraints/Part/BulkImportJobStatusConstraint.php b/src/DataTables/Filters/Constraints/Part/BulkImportJobStatusConstraint.php new file mode 100644 index 000000000..d9451577b --- /dev/null +++ b/src/DataTables/Filters/Constraints/Part/BulkImportJobStatusConstraint.php @@ -0,0 +1,64 @@ +. + */ + +namespace App\DataTables\Filters\Constraints\Part; + +use App\DataTables\Filters\Constraints\AbstractConstraint; +use App\DataTables\Filters\Constraints\ChoiceConstraint; +use App\Entity\InfoProviderSystem\BulkInfoProviderImportJobPart; +use Doctrine\ORM\QueryBuilder; + +class BulkImportJobStatusConstraint extends ChoiceConstraint +{ + + public function __construct() + { + parent::__construct('bulk_import_job_status'); + } + + public function apply(QueryBuilder $queryBuilder): void + { + // Do not apply a filter if values are empty or operator is null + if (!$this->isEnabled()) { + return; + } + + // Use EXISTS subquery to check if part has a job with the specified status(es) + $existsSubquery = $queryBuilder->getEntityManager()->createQueryBuilder(); + $existsSubquery->select('1') + ->from(BulkInfoProviderImportJobPart::class, 'bip_status') + ->join('bip_status.job', 'job_status') + ->where('bip_status.part = part.id'); + + // Add status conditions based on operator + if ($this->operator === 'ANY') { + $existsSubquery->andWhere('job_status.status IN (:job_status_values)'); + $queryBuilder->andWhere('EXISTS (' . $existsSubquery->getDQL() . ')'); + $queryBuilder->setParameter('job_status_values', $this->value); + } elseif ($this->operator === 'NONE') { + $existsSubquery->andWhere('job_status.status IN (:job_status_values)'); + $queryBuilder->andWhere('NOT EXISTS (' . $existsSubquery->getDQL() . ')'); + $queryBuilder->setParameter('job_status_values', $this->value); + } + } +} diff --git a/src/DataTables/Filters/Constraints/Part/BulkImportPartStatusConstraint.php b/src/DataTables/Filters/Constraints/Part/BulkImportPartStatusConstraint.php new file mode 100644 index 000000000..7656a2907 --- /dev/null +++ b/src/DataTables/Filters/Constraints/Part/BulkImportPartStatusConstraint.php @@ -0,0 +1,61 @@ +. + */ + +namespace App\DataTables\Filters\Constraints\Part; + +use App\DataTables\Filters\Constraints\ChoiceConstraint; +use App\Entity\InfoProviderSystem\BulkInfoProviderImportJobPart; +use Doctrine\ORM\QueryBuilder; + +class BulkImportPartStatusConstraint extends ChoiceConstraint +{ + public function __construct() + { + parent::__construct('bulk_import_part_status'); + } + + public function apply(QueryBuilder $queryBuilder): void + { + // Do not apply a filter if values are empty or operator is null + if (!$this->isEnabled()) { + return; + } + + // Use EXISTS subquery to check if part has the specified status(es) + $existsSubquery = $queryBuilder->getEntityManager()->createQueryBuilder(); + $existsSubquery->select('1') + ->from(BulkInfoProviderImportJobPart::class, 'bip_part_status') + ->where('bip_part_status.part = part.id'); + + // Add status conditions based on operator + if ($this->operator === 'ANY') { + $existsSubquery->andWhere('bip_part_status.status IN (:part_status_values)'); + $queryBuilder->andWhere('EXISTS (' . $existsSubquery->getDQL() . ')'); + $queryBuilder->setParameter('part_status_values', $this->value); + } elseif ($this->operator === 'NONE') { + $existsSubquery->andWhere('bip_part_status.status IN (:part_status_values)'); + $queryBuilder->andWhere('NOT EXISTS (' . $existsSubquery->getDQL() . ')'); + $queryBuilder->setParameter('part_status_values', $this->value); + } + } +} diff --git a/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php b/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php index eb96ad33c..011824e50 100644 --- a/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php +++ b/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php @@ -28,7 +28,7 @@ class LessThanDesiredConstraint extends BooleanConstraint { - public function __construct(string $property = null, string $identifier = null, ?bool $default_value = null) + public function __construct(?string $property = null, ?string $identifier = null, ?bool $default_value = null) { parent::__construct($property ?? '( SELECT COALESCE(SUM(ld_partLot.amount), 0.0) diff --git a/src/DataTables/Filters/Constraints/Part/TagsConstraint.php b/src/DataTables/Filters/Constraints/Part/TagsConstraint.php index cb17de5cc..2b28e6b44 100644 --- a/src/DataTables/Filters/Constraints/Part/TagsConstraint.php +++ b/src/DataTables/Filters/Constraints/Part/TagsConstraint.php @@ -30,7 +30,7 @@ class TagsConstraint extends AbstractConstraint { final public const ALLOWED_OPERATOR_VALUES = ['ANY', 'ALL', 'NONE']; - public function __construct(string $property, string $identifier = null, + public function __construct(string $property, ?string $identifier = null, protected ?string $value = null, protected ?string $operator = '') { @@ -88,7 +88,7 @@ protected function getExpressionForTag(QueryBuilder $queryBuilder, string $tag): //Escape any %, _ or \ in the tag $tag = addcslashes($tag, '%_\\'); - $tag_identifier_prefix = uniqid($this->identifier . '_', false); + $tag_identifier_prefix = $this->generateParameterIdentifier('tag'); $expr = $queryBuilder->expr(); diff --git a/src/DataTables/Filters/Constraints/TextConstraint.php b/src/DataTables/Filters/Constraints/TextConstraint.php index 671f90ef2..c6a6fe198 100644 --- a/src/DataTables/Filters/Constraints/TextConstraint.php +++ b/src/DataTables/Filters/Constraints/TextConstraint.php @@ -32,7 +32,7 @@ class TextConstraint extends AbstractConstraint /** * @param string $value */ - public function __construct(string $property, string $identifier = null, /** + public function __construct(string $property, ?string $identifier = null, /** * @var string|null The value to compare to */ protected ?string $value = null, /** @@ -96,14 +96,15 @@ public function apply(QueryBuilder $queryBuilder): void //The CONTAINS, LIKE, STARTS and ENDS operators use the LIKE operator, but we have to build the value string differently $like_value = null; + $escaped_value = str_replace(['%', '_'], ['\%', '\_'], $this->value); if ($this->operator === 'LIKE') { - $like_value = $this->value; + $like_value = $this->value; //Here we do not escape anything, as the user may provide % and _ wildcards } elseif ($this->operator === 'STARTS') { - $like_value = $this->value . '%'; + $like_value = $escaped_value . '%'; } elseif ($this->operator === 'ENDS') { - $like_value = '%' . $this->value; + $like_value = '%' . $escaped_value; } elseif ($this->operator === 'CONTAINS') { - $like_value = '%' . $this->value . '%'; + $like_value = '%' . $escaped_value . '%'; } if ($like_value !== null) { diff --git a/src/DataTables/Filters/LogFilter.php b/src/DataTables/Filters/LogFilter.php index 35d32e743..38dc2191e 100644 --- a/src/DataTables/Filters/LogFilter.php +++ b/src/DataTables/Filters/LogFilter.php @@ -22,6 +22,7 @@ */ namespace App\DataTables\Filters; +use App\DataTables\Filters\Constraints\AbstractConstraint; use App\DataTables\Filters\Constraints\ChoiceConstraint; use App\DataTables\Filters\Constraints\DateTimeConstraint; use App\DataTables\Filters\Constraints\EntityConstraint; @@ -44,6 +45,9 @@ class LogFilter implements FilterInterface public function __construct() { + //Must be done for every new set of attachment filters, to ensure deterministic parameter names. + AbstractConstraint::resetParameterCounter(); + $this->timestamp = new DateTimeConstraint('log.timestamp'); $this->dbId = new IntConstraint('log.id'); $this->level = new ChoiceConstraint('log.level'); diff --git a/src/DataTables/Filters/PartFilter.php b/src/DataTables/Filters/PartFilter.php index ff98c76f9..cf185dfd7 100644 --- a/src/DataTables/Filters/PartFilter.php +++ b/src/DataTables/Filters/PartFilter.php @@ -22,12 +22,16 @@ */ namespace App\DataTables\Filters; +use App\DataTables\Filters\Constraints\AbstractConstraint; use App\DataTables\Filters\Constraints\BooleanConstraint; use App\DataTables\Filters\Constraints\ChoiceConstraint; use App\DataTables\Filters\Constraints\DateTimeConstraint; use App\DataTables\Filters\Constraints\EntityConstraint; use App\DataTables\Filters\Constraints\IntConstraint; use App\DataTables\Filters\Constraints\NumberConstraint; +use App\DataTables\Filters\Constraints\Part\BulkImportJobExistsConstraint; +use App\DataTables\Filters\Constraints\Part\BulkImportJobStatusConstraint; +use App\DataTables\Filters\Constraints\Part\BulkImportPartStatusConstraint; use App\DataTables\Filters\Constraints\Part\LessThanDesiredConstraint; use App\DataTables\Filters\Constraints\Part\ParameterConstraint; use App\DataTables\Filters\Constraints\Part\TagsConstraint; @@ -37,6 +41,7 @@ use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; +use App\Entity\Parts\PartCustomState; use App\Entity\Parts\PartLot; use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; @@ -82,6 +87,7 @@ class PartFilter implements FilterInterface public readonly EntityConstraint $lotOwner; public readonly EntityConstraint $measurementUnit; + public readonly EntityConstraint $partCustomState; public readonly TextConstraint $manufacturer_product_url; public readonly TextConstraint $manufacturer_product_number; public readonly IntConstraint $attachmentsCount; @@ -101,8 +107,19 @@ class PartFilter implements FilterInterface public readonly TextConstraint $bomName; public readonly TextConstraint $bomComment; + /************************************************* + * Bulk Import Job tab + *************************************************/ + + public readonly BulkImportJobExistsConstraint $inBulkImportJob; + public readonly BulkImportJobStatusConstraint $bulkImportJobStatus; + public readonly BulkImportPartStatusConstraint $bulkImportPartStatus; + public function __construct(NodesListBuilder $nodesListBuilder) { + //Must be done for every new set of attachment filters, to ensure deterministic parameter names. + AbstractConstraint::resetParameterCounter(); + $this->name = new TextConstraint('part.name'); $this->description = new TextConstraint('part.description'); $this->comment = new TextConstraint('part.comment'); @@ -113,6 +130,7 @@ public function __construct(NodesListBuilder $nodesListBuilder) $this->favorite = new BooleanConstraint('part.favorite'); $this->needsReview = new BooleanConstraint('part.needs_review'); $this->measurementUnit = new EntityConstraint($nodesListBuilder, MeasurementUnit::class, 'part.partUnit'); + $this->partCustomState = new EntityConstraint($nodesListBuilder, PartCustomState::class, 'part.partCustomState'); $this->mass = new NumberConstraint('part.mass'); $this->dbId = new IntConstraint('part.id'); $this->ipn = new TextConstraint('part.ipn'); @@ -126,7 +144,7 @@ public function __construct(NodesListBuilder $nodesListBuilder) */ $this->amountSum = (new IntConstraint('( SELECT COALESCE(SUM(__partLot.amount), 0.0) - FROM '.PartLot::class.' __partLot + FROM ' . PartLot::class . ' __partLot WHERE __partLot.part = part.id AND __partLot.instock_unknown = false AND (__partLot.expiration_date IS NULL OR __partLot.expiration_date > CURRENT_DATE()) @@ -162,6 +180,11 @@ public function __construct(NodesListBuilder $nodesListBuilder) $this->bomName = new TextConstraint('_projectBomEntries.name'); $this->bomComment = new TextConstraint('_projectBomEntries.comment'); + // Bulk Import Job filters + $this->inBulkImportJob = new BulkImportJobExistsConstraint(); + $this->bulkImportJobStatus = new BulkImportJobStatusConstraint(); + $this->bulkImportPartStatus = new BulkImportPartStatusConstraint(); + } public function apply(QueryBuilder $queryBuilder): void diff --git a/src/DataTables/Filters/PartSearchFilter.php b/src/DataTables/Filters/PartSearchFilter.php index 6e2e58949..aa8c20f43 100644 --- a/src/DataTables/Filters/PartSearchFilter.php +++ b/src/DataTables/Filters/PartSearchFilter.php @@ -21,6 +21,7 @@ * along with this program. If not, see . */ namespace App\DataTables\Filters; +use App\DataTables\Filters\Constraints\AbstractConstraint; use Doctrine\ORM\QueryBuilder; class PartSearchFilter implements FilterInterface @@ -143,6 +144,8 @@ public function apply(QueryBuilder $queryBuilder): void if ($this->regex) { $queryBuilder->setParameter('search_query', $this->keyword); } else { + //Escape % and _ characters in the keyword + $this->keyword = str_replace(['%', '_'], ['\%', '\_'], $this->keyword); $queryBuilder->setParameter('search_query', '%' . $this->keyword . '%'); } } diff --git a/src/DataTables/Helpers/ColumnSortHelper.php b/src/DataTables/Helpers/ColumnSortHelper.php index 05bd81825..b7b5b567c 100644 --- a/src/DataTables/Helpers/ColumnSortHelper.php +++ b/src/DataTables/Helpers/ColumnSortHelper.php @@ -72,7 +72,8 @@ public function reset(): void * Apply the visibility configuration to the given DataTable and configure the columns. * @param DataTable $dataTable * @param string|array $visible_columns Either a list or a comma separated string of column names, which should - * be visible by default. If a column is not listed here, it will be hidden by default. + * be visible by default. If a column is not listed here, it will be hidden by default. If an array of enum values are passed, + * their value will be used as the column name. * @return void */ public function applyVisibilityAndConfigureColumns(DataTable $dataTable, string|array $visible_columns, @@ -83,6 +84,14 @@ public function applyVisibilityAndConfigureColumns(DataTable $dataTable, string| $visible_columns = array_map(trim(...), explode(",", $visible_columns)); } + //If $visible_columns is a list of enum values, convert them to the column names + foreach ($visible_columns as &$value) { + if ($value instanceof \BackedEnum) { + $value = $value->value; + } + } + unset ($value); + $processed_columns = []; //First add all columns which visibility is not configurable diff --git a/src/DataTables/PartsDataTable.php b/src/DataTables/PartsDataTable.php index f62d90837..0baee6300 100644 --- a/src/DataTables/PartsDataTable.php +++ b/src/DataTables/PartsDataTable.php @@ -45,6 +45,7 @@ use App\Entity\ProjectSystem\Project; use App\Services\EntityURLGenerator; use App\Services\Formatters\AmountFormatter; +use App\Settings\BehaviorSettings\TableSettings; use Doctrine\ORM\AbstractQuery; use Doctrine\ORM\QueryBuilder; use Omines\DataTablesBundle\Adapter\Doctrine\ORM\SearchCriteriaProvider; @@ -57,14 +58,16 @@ final class PartsDataTable implements DataTableTypeInterface { + const LENGTH_MENU = [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]]; + public function __construct( private readonly EntityURLGenerator $urlGenerator, private readonly TranslatorInterface $translator, private readonly AmountFormatter $amountFormatter, private readonly PartDataTableHelper $partDataTableHelper, private readonly Security $security, - private readonly string $visible_columns, private readonly ColumnSortHelper $csh, + private readonly TableSettings $tableSettings, ) { } @@ -139,23 +142,25 @@ public function configure(DataTable $dataTable, array $options): void 'label' => $this->translator->trans('part.table.storeLocations'), //We need to use a aggregate function to get the first store location, as we have a one-to-many relation 'orderField' => 'NATSORT(MIN(_storelocations.name))', - 'render' => fn ($value, Part $context) => $this->partDataTableHelper->renderStorageLocations($context), + 'render' => fn($value, Part $context) => $this->partDataTableHelper->renderStorageLocations($context), ], alias: 'storage_location') ->add('amount', TextColumn::class, [ 'label' => $this->translator->trans('part.table.amount'), - 'render' => fn ($value, Part $context) => $this->partDataTableHelper->renderAmount($context), + 'render' => fn($value, Part $context) => $this->partDataTableHelper->renderAmount($context), 'orderField' => 'amountSum' ]) ->add('minamount', TextColumn::class, [ 'label' => $this->translator->trans('part.table.minamount'), - 'render' => fn($value, Part $context): string => htmlspecialchars($this->amountFormatter->format($value, - $context->getPartUnit())), + 'render' => fn($value, Part $context): string => htmlspecialchars($this->amountFormatter->format( + $value, + $context->getPartUnit() + )), ]) ->add('partUnit', TextColumn::class, [ 'label' => $this->translator->trans('part.table.partUnit'), 'orderField' => 'NATSORT(_partUnit.name)', - 'render' => function($value, Part $context): string { + 'render' => function ($value, Part $context): string { $partUnit = $context->getPartUnit(); if ($partUnit === null) { return ''; @@ -164,11 +169,24 @@ public function configure(DataTable $dataTable, array $options): void $tmp = htmlspecialchars($partUnit->getName()); if ($partUnit->getUnit()) { - $tmp .= ' ('.htmlspecialchars($partUnit->getUnit()).')'; + $tmp .= ' (' . htmlspecialchars($partUnit->getUnit()) . ')'; } return $tmp; } ]) + ->add('partCustomState', TextColumn::class, [ + 'label' => $this->translator->trans('part.table.partCustomState'), + 'orderField' => 'NATSORT(_partCustomState.name)', + 'render' => function($value, Part $context): string { + $partCustomState = $context->getPartCustomState(); + + if ($partCustomState === null) { + return ''; + } + + return htmlspecialchars($partCustomState->getName()); + } + ]) ->add('addedDate', LocaleDateTimeColumn::class, [ 'label' => $this->translator->trans('part.table.addedDate'), ]) @@ -227,7 +245,7 @@ public function configure(DataTable $dataTable, array $options): void } if (count($projects) > $max) { - $tmp .= ", + ".(count($projects) - $max); + $tmp .= ", + " . (count($projects) - $max); } return $tmp; @@ -244,7 +262,7 @@ public function configure(DataTable $dataTable, array $options): void ]); //Apply the user configured order and visibility and add the columns to the table - $this->csh->applyVisibilityAndConfigureColumns($dataTable, $this->visible_columns, + $this->csh->applyVisibilityAndConfigureColumns($dataTable, $this->tableSettings->partsDefaultColumns, "TABLE_PARTS_DEFAULT_COLUMNS"); $dataTable->addOrderBy('name') @@ -304,6 +322,7 @@ private function getDetailQuery(QueryBuilder $builder, array $filter_results): v ->addSelect('footprint') ->addSelect('manufacturer') ->addSelect('partUnit') + ->addSelect('partCustomState') ->addSelect('master_picture_attachment') ->addSelect('footprint_attachment') ->addSelect('partLots') @@ -322,6 +341,7 @@ private function getDetailQuery(QueryBuilder $builder, array $filter_results): v ->leftJoin('orderdetails.supplier', 'suppliers') ->leftJoin('part.attachments', 'attachments') ->leftJoin('part.partUnit', 'partUnit') + ->leftJoin('part.partCustomState', 'partCustomState') ->leftJoin('part.parameters', 'parameters') ->where('part.id IN (:ids)') ->setParameter('ids', $ids) @@ -339,6 +359,7 @@ private function getDetailQuery(QueryBuilder $builder, array $filter_results): v ->addGroupBy('suppliers') ->addGroupBy('attachments') ->addGroupBy('partUnit') + ->addGroupBy('partCustomState') ->addGroupBy('parameters'); //Get the results in the same order as the IDs were passed @@ -363,7 +384,7 @@ private function addJoins(QueryBuilder $builder): QueryBuilder $builder->addSelect( '( SELECT COALESCE(SUM(partLot.amount), 0.0) - FROM '.PartLot::class.' partLot + FROM ' . PartLot::class . ' partLot WHERE partLot.part = part.id AND partLot.instock_unknown = false AND (partLot.expiration_date IS NULL OR partLot.expiration_date > CURRENT_DATE()) @@ -410,6 +431,10 @@ private function addJoins(QueryBuilder $builder): QueryBuilder $builder->leftJoin('part.partUnit', '_partUnit'); $builder->addGroupBy('_partUnit'); } + if (str_contains($dql, '_partCustomState')) { + $builder->leftJoin('part.partCustomState', '_partCustomState'); + $builder->addGroupBy('_partCustomState'); + } if (str_contains($dql, '_parameters')) { $builder->leftJoin('part.parameters', '_parameters'); //Do not group by many-to-* relations, as it would restrict the COUNT having clauses to be maximum 1 @@ -420,6 +445,13 @@ private function addJoins(QueryBuilder $builder): QueryBuilder //Do not group by many-to-* relations, as it would restrict the COUNT having clauses to be maximum 1 //$builder->addGroupBy('_projectBomEntries'); } + if (str_contains($dql, '_jobPart')) { + $builder->leftJoin('part.bulkImportJobParts', '_jobPart'); + $builder->leftJoin('_jobPart.job', '_bulkImportJob'); + //Do not group by many-to-* relations, as it would restrict the COUNT having clauses to be maximum 1 + //$builder->addGroupBy('_jobPart'); + //$builder->addGroupBy('_bulkImportJob'); + } return $builder; } diff --git a/src/Doctrine/Functions/ILike.php b/src/Doctrine/Functions/ILike.php index 5246220a2..ff2d2163c 100644 --- a/src/Doctrine/Functions/ILike.php +++ b/src/Doctrine/Functions/ILike.php @@ -56,7 +56,6 @@ public function getSql(SqlWalker $sqlWalker): string { $platform = $sqlWalker->getConnection()->getDatabasePlatform(); - // if ($platform instanceof AbstractMySQLPlatform || $platform instanceof SQLitePlatform) { $operator = 'LIKE'; } elseif ($platform instanceof PostgreSQLPlatform) { @@ -66,6 +65,12 @@ public function getSql(SqlWalker $sqlWalker): string throw new \RuntimeException('Platform ' . gettype($platform) . ' does not support case insensitive like expressions.'); } - return '(' . $this->value->dispatch($sqlWalker) . ' ' . $operator . ' ' . $this->expr->dispatch($sqlWalker) . ')'; + $escape = ""; + if ($platform instanceof SQLitePlatform) { + //SQLite needs ESCAPE explicitly defined backslash as escape character + $escape = " ESCAPE '\\'"; + } + + return '(' . $this->value->dispatch($sqlWalker) . ' ' . $operator . ' ' . $this->expr->dispatch($sqlWalker) . $escape . ')'; } -} \ No newline at end of file +} diff --git a/src/Doctrine/Migration/ContainerAwareMigrationFactory.php b/src/Doctrine/Migration/ContainerAwareMigrationFactory.php new file mode 100644 index 000000000..81565c0e6 --- /dev/null +++ b/src/Doctrine/Migration/ContainerAwareMigrationFactory.php @@ -0,0 +1,55 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Doctrine\Migration; + +use App\Services\UserSystem\PermissionPresetsHelper; +use Doctrine\Migrations\AbstractMigration; +use Doctrine\Migrations\Version\MigrationFactory; +use Symfony\Component\DependencyInjection\Attribute\AsDecorator; +use Symfony\Component\DependencyInjection\Attribute\AutowireLocator; +use Psr\Container\ContainerInterface; + +#[AsDecorator("doctrine.migrations.migrations_factory")] +class ContainerAwareMigrationFactory implements MigrationFactory +{ + public function __construct(private readonly MigrationFactory $decorated, + //List all services that should be available in migrations here + #[AutowireLocator([ + PermissionPresetsHelper::class + ])] + private readonly ContainerInterface $container) + { + } + + public function createVersion(string $migrationClassName): AbstractMigration + { + $migration = $this->decorated->createVersion($migrationClassName); + + if ($migration instanceof ContainerAwareMigrationInterface) { + $migration->setContainer($this->container); + } + + return $migration; + } +} \ No newline at end of file diff --git a/src/Doctrine/Migration/ContainerAwareMigrationInterface.php b/src/Doctrine/Migration/ContainerAwareMigrationInterface.php new file mode 100644 index 000000000..bd92116a6 --- /dev/null +++ b/src/Doctrine/Migration/ContainerAwareMigrationInterface.php @@ -0,0 +1,31 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Doctrine\Migration; + +use Psr\Container\ContainerInterface; + +interface ContainerAwareMigrationInterface +{ + public function setContainer(?ContainerInterface $container = null): void; +} \ No newline at end of file diff --git a/src/Doctrine/Types/ArrayType.php b/src/Doctrine/Types/ArrayType.php deleted file mode 100644 index daab9b75a..000000000 --- a/src/Doctrine/Types/ArrayType.php +++ /dev/null @@ -1,116 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace App\Doctrine\Types; - -use Doctrine\DBAL\Platforms\AbstractPlatform; -use Doctrine\DBAL\Types\Exception\SerializationFailed; -use Doctrine\DBAL\Types\Type; -use Doctrine\Deprecations\Deprecation; - -use function is_resource; -use function restore_error_handler; -use function serialize; -use function set_error_handler; -use function stream_get_contents; -use function unserialize; - -use const E_DEPRECATED; -use const E_USER_DEPRECATED; - -/** - * This class is taken from doctrine ORM 3.8. https://github.com/doctrine/dbal/blob/3.8.x/src/Types/ArrayType.php - * - * It was removed in doctrine ORM 4.0. However, we require it for backward compatibility with WebauthnKey. - * Therefore, we manually added it here as a custom type as a forward compatibility layer. - */ -class ArrayType extends Type -{ - /** - * {@inheritDoc} - */ - public function getSQLDeclaration(array $column, AbstractPlatform $platform): string - { - return $platform->getClobTypeDeclarationSQL($column); - } - - /** - * {@inheritDoc} - */ - public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): string - { - return serialize($value); - } - - /** - * {@inheritDoc} - */ - public function convertToPHPValue(mixed $value, AbstractPlatform $platform): mixed - { - if ($value === null) { - return null; - } - - $value = is_resource($value) ? stream_get_contents($value) : $value; - - set_error_handler(function (int $code, string $message): bool { - if ($code === E_DEPRECATED || $code === E_USER_DEPRECATED) { - return false; - } - - //Change to original code. Use SerializationFailed instead of ConversionException. - throw new SerializationFailed("Serialization failed (Code $code): " . $message); - }); - - try { - //Change to original code. Use false for allowed_classes, to avoid unsafe unserialization of objects. - return unserialize($value, ['allowed_classes' => false]); - } finally { - restore_error_handler(); - } - } - - /** - * {@inheritDoc} - */ - public function getName(): string - { - return "array"; - } - - /** - * {@inheritDoc} - * - * @deprecated - */ - public function requiresSQLCommentHint(AbstractPlatform $platform): bool - { - Deprecation::triggerIfCalledFromOutside( - 'doctrine/dbal', - 'https://github.com/doctrine/dbal/pull/5509', - '%s is deprecated.', - __METHOD__, - ); - - return true; - } -} \ No newline at end of file diff --git a/src/Entity/Attachments/Attachment.php b/src/Entity/Attachments/Attachment.php index 30d9e2570..35a6a5293 100644 --- a/src/Entity/Attachments/Attachment.php +++ b/src/Entity/Attachments/Attachment.php @@ -33,23 +33,24 @@ use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; -use App\ApiPlatform\DocumentedAPIProperty; +use App\ApiPlatform\DocumentedAPIProperties\DocumentedAPIProperty; use App\ApiPlatform\Filter\EntityFilter; use App\ApiPlatform\Filter\LikeFilter; use App\ApiPlatform\HandleAttachmentsUploadsProcessor; -use App\Repository\AttachmentRepository; -use App\EntityListeners\AttachmentDeleteListener; -use Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractNamedDBElement; +use App\EntityListeners\AttachmentDeleteListener; +use App\Repository\AttachmentRepository; use App\Validator\Constraints\Selectable; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; +use InvalidArgumentException; +use LogicException; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Serializer\Annotation\SerializedName; use Symfony\Component\Serializer\Attribute\DiscriminatorMap; use Symfony\Component\Validator\Constraints as Assert; + use function in_array; -use InvalidArgumentException; -use LogicException; /** * Class Attachment. @@ -78,11 +79,16 @@ denormalizationContext: ['groups' => ['attachment:write', 'attachment:write:standalone', 'api:basic:write'], 'openapi_definition_name' => 'Write'], processor: HandleAttachmentsUploadsProcessor::class, )] -#[DocumentedAPIProperty(schemaName: 'Attachment-Read', property: 'media_url', type: 'string', nullable: true, - description: 'The URL to the file, where the attachment file can be downloaded. This can be an internal or external URL.', - example: '/media/part/2/bc547-6508afa5a79c8.pdf')] -#[DocumentedAPIProperty(schemaName: 'Attachment-Read', property: 'thumbnail_url', type: 'string', nullable: true, - description: 'The URL to a thumbnail version of this file. This only exists for internal picture attachments.')] +//This property is added by the denormalizer in order to resolve the placeholder +#[DocumentedAPIProperty( + schemaName: 'Attachment-Read', property: 'internal_path', type: 'string', nullable: false, + description: 'The URL to the internally saved copy of the file, if one exists', + example: '/media/part/2/bc547-6508afa5a79c8.pdf' +)] +#[DocumentedAPIProperty( + schemaName: 'Attachment-Read', property: 'thumbnail_url', type: 'string', nullable: true, + description: 'The URL to a thumbnail version of this file. This only exists for internal picture attachments.' +)] #[ApiFilter(LikeFilter::class, properties: ["name"])] #[ApiFilter(EntityFilter::class, properties: ["attachment_type"])] #[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)] @@ -91,8 +97,8 @@ #[DiscriminatorMap(typeProperty: '_type', mapping: self::API_DISCRIMINATOR_MAP)] abstract class Attachment extends AbstractNamedDBElement { - private const ORM_DISCRIMINATOR_MAP = ['PartDB\Part' => PartAttachment::class, 'Part' => PartAttachment::class, - 'PartDB\Device' => ProjectAttachment::class, 'Device' => ProjectAttachment::class, 'AttachmentType' => AttachmentTypeAttachment::class, + private const ORM_DISCRIMINATOR_MAP = ['Part' => PartAttachment::class, 'PartCustomState' => PartCustomStateAttachment::class, 'Device' => ProjectAttachment::class, + 'AttachmentType' => AttachmentTypeAttachment::class, 'Category' => CategoryAttachment::class, 'Footprint' => FootprintAttachment::class, 'Manufacturer' => ManufacturerAttachment::class, 'Currency' => CurrencyAttachment::class, 'Group' => GroupAttachment::class, 'MeasurementUnit' => MeasurementUnitAttachment::class, 'Storelocation' => StorageLocationAttachment::class, 'Supplier' => SupplierAttachment::class, @@ -101,7 +107,8 @@ abstract class Attachment extends AbstractNamedDBElement /* * The discriminator map used for API platform. The key should be the same as the api platform short type (the @type JSONLD field). */ - private const API_DISCRIMINATOR_MAP = ["Part" => PartAttachment::class, "Project" => ProjectAttachment::class, "AttachmentType" => AttachmentTypeAttachment::class, + private const API_DISCRIMINATOR_MAP = ["Part" => PartAttachment::class, "PartCustomState" => PartCustomStateAttachment::class, "Project" => ProjectAttachment::class, + "AttachmentType" => AttachmentTypeAttachment::class, "Category" => CategoryAttachment::class, "Footprint" => FootprintAttachment::class, "Manufacturer" => ManufacturerAttachment::class, "Currency" => CurrencyAttachment::class, "Group" => GroupAttachment::class, "MeasurementUnit" => MeasurementUnitAttachment::class, "StorageLocation" => StorageLocationAttachment::class, "Supplier" => SupplierAttachment::class, "User" => UserAttachment::class, "LabelProfile" => LabelAttachment::class]; @@ -119,10 +126,6 @@ abstract class Attachment extends AbstractNamedDBElement */ final public const MODEL_EXTS = ['x3d']; - /** - * When the path begins with one of the placeholders. - */ - final public const INTERNAL_PLACEHOLDER = ['%BASE%', '%MEDIA%', '%SECURE%']; /** * @var array placeholders for attachments which using built in files @@ -152,10 +155,21 @@ abstract class Attachment extends AbstractNamedDBElement protected ?string $original_filename = null; /** - * @var string The path to the file relative to a placeholder path like %MEDIA% + * @var string|null If a copy of the file is stored internally, the path to the file relative to a placeholder + * path like %MEDIA% */ - #[ORM\Column(name: 'path', type: Types::STRING)] - protected string $path = ''; + #[ORM\Column(type: Types::STRING, nullable: true)] + protected ?string $internal_path = null; + + + /** + * @var string|null The path to the external source if the file is stored externally or was downloaded from an + * external source. Null if there is no external source. + */ + #[ORM\Column(type: Types::STRING, nullable: true)] + #[Groups(['attachment:read'])] + #[ApiProperty(example: 'http://example.com/image.jpg')] + protected ?string $external_path = null; /** * @var string the name of this element @@ -237,7 +251,7 @@ public function setUpload(?AttachmentUpload $upload): Attachment /** * Check if this attachment is a picture (analyse the file's extension). - * If the link is external, it is assumed that this is true. + * If the link is only external and doesn't contain an extension, it is assumed that this is true. * * @return bool * true if the file extension is a picture extension * * otherwise false @@ -245,54 +259,67 @@ public function setUpload(?AttachmentUpload $upload): Attachment #[Groups(['attachment:read'])] public function isPicture(): bool { - if ($this->isExternal()) { + if($this->hasInternal()){ + + $extension = pathinfo($this->getInternalPath(), PATHINFO_EXTENSION); + + return in_array(strtolower($extension), static::PICTURE_EXTS, true); + + } + if ($this->hasExternal()) { //Check if we can extract a file extension from the URL - $extension = pathinfo(parse_url(http://github.com/$this-%3Epath,%20PHP_URL_PATH) ?? '', PATHINFO_EXTENSION); + $extension = pathinfo(parse_url(http://github.com/$this-%3EgetExternalPath(), PHP_URL_PATH) ?? '', PATHINFO_EXTENSION); //If no extension is found or it is known picture extension, we assume that this is a picture extension return $extension === '' || in_array(strtolower($extension), static::PICTURE_EXTS, true); } - - $extension = pathinfo($this->getPath(), PATHINFO_EXTENSION); - - return in_array(strtolower($extension), static::PICTURE_EXTS, true); + //File doesn't have an internal, nor an external copy. This shouldn't happen, but it certainly isn't a picture... + return false; } /** * Check if this attachment is a 3D model and therefore can be directly shown to user. - * If the attachment is external, false is returned (3D Models must be internal). + * If no internal copy exists, false is returned (3D Models must be internal). */ #[Groups(['attachment:read'])] #[SerializedName('3d_model')] public function is3DModel(): bool { //We just assume that 3D Models are internally saved, otherwise we get problems loading them. - if ($this->isExternal()) { + if (!$this->hasInternal()) { return false; } - $extension = pathinfo($this->getPath(), PATHINFO_EXTENSION); + $extension = pathinfo($this->getInternalPath(), PATHINFO_EXTENSION); return in_array(strtolower($extension), static::MODEL_EXTS, true); } /** - * Checks if the attachment file is externally saved (the database saves an URL). + * Checks if this attachment has a path to an external file * - * @return bool true, if the file is saved externally + * @return bool true, if there is a path to an external file + * @phpstan-assert-if-true non-empty-string $this->external_path + * @phpstan-assert-if-true non-empty-string $this->getExternalPath()) */ #[Groups(['attachment:read'])] - public function isExternal(): bool + public function hasExternal(): bool { - //When path is empty, this attachment can not be external - if ($this->path === '') { - return false; - } - - //After the %PLACEHOLDER% comes a slash, so we can check if we have a placeholder via explode - $tmp = explode('/', $this->path); + return $this->external_path !== null && $this->external_path !== ''; + } - return !in_array($tmp[0], array_merge(static::INTERNAL_PLACEHOLDER, static::BUILTIN_PLACEHOLDER), true); + /** + * Checks if this attachment has a path to an internal file. + * Does not check if the file exists. + * + * @return bool true, if there is a path to an internal file + * @phpstan-assert-if-true non-empty-string $this->internal_path + * @phpstan-assert-if-true non-empty-string $this->getInternalPath()) + */ + #[Groups(['attachment:read'])] + public function hasInternal(): bool + { + return $this->internal_path !== null && $this->internal_path !== ''; } /** @@ -305,8 +332,12 @@ public function isExternal(): bool #[SerializedName('private')] public function isSecure(): bool { + if ($this->internal_path === null) { + return false; + } + //After the %PLACEHOLDER% comes a slash, so we can check if we have a placeholder via explode - $tmp = explode('/', $this->path); + $tmp = explode('/', $this->internal_path); return '%SECURE%' === $tmp[0]; } @@ -320,7 +351,11 @@ public function isSecure(): bool #[Groups(['attachment:read'])] public function isBuiltIn(): bool { - return static::checkIfBuiltin($this->path); + if ($this->internal_path === null) { + return false; + } + + return static::checkIfBuiltin($this->internal_path); } /******************************************************************************** @@ -332,13 +367,13 @@ public function isBuiltIn(): bool /** * Returns the extension of the file referenced via the attachment. * For a path like %BASE/path/foo.bar, bar will be returned. - * If this attachment is external null is returned. + * If this attachment is only external null is returned. * * @return string|null the file extension in lower case */ public function getExtension(): ?string { - if ($this->isExternal()) { + if (!$this->hasInternal()) { return null; } @@ -346,7 +381,7 @@ public function getExtension(): ?string return strtolower(pathinfo($this->original_filename, PATHINFO_EXTENSION)); } - return strtolower(pathinfo($this->getPath(), PATHINFO_EXTENSION)); + return strtolower(pathinfo($this->getInternalPath(), PATHINFO_EXTENSION)); } /** @@ -361,52 +396,54 @@ public function getElement(): ?AttachmentContainingDBElement } /** - * The URL to the external file, or the path to the built-in file. + * The URL to the external file, or the path to the built-in file, but not paths to uploaded files. * Returns null, if the file is not external (and not builtin). + * The output of this function is such, that no changes occur when it is fed back into setURL(). + * Required for the Attachment form field. */ - #[Groups(['attachment:read'])] - #[SerializedName('url')] public function getURL(): ?string { - if (!$this->isExternal() && !$this->isBuiltIn()) { - return null; + if($this->hasExternal()){ + return $this->getExternalPath(); } - - return $this->path; + if($this->isBuiltIn()){ + return $this->getInternalPath(); + } + return null; } /** * Returns the hostname where the external file is stored. - * Returns null, if the file is not external. + * Returns null, if there is no external path. */ public function getHost(): ?string { - if (!$this->isExternal()) { + if (!$this->hasExternal()) { return null; } - return parse_url(http://github.com/(string) $this->getURL(), PHP_URL_HOST); + return parse_url(http://github.com/$this-%3EgetExternalPath(), PHP_URL_HOST); } - /** - * Get the filepath, relative to %BASE%. - * - * @return string A string like %BASE/path/foo.bar - */ - public function getPath(): string + public function getInternalPath(): ?string { - return $this->path; + return $this->internal_path; + } + + public function getExternalPath(): ?string + { + return $this->external_path; } /** * Returns the filename of the attachment. * For a path like %BASE/path/foo.bar, foo.bar will be returned. * - * If the path is a URL (can be checked via isExternal()), null will be returned. + * If there is no internal copy of the file, null will be returned. */ public function getFilename(): ?string { - if ($this->isExternal()) { + if (!$this->hasInternal()) { return null; } @@ -415,7 +452,7 @@ public function getFilename(): ?string return $this->original_filename; } - return pathinfo($this->getPath(), PATHINFO_BASENAME); + return pathinfo($this->getInternalPath(), PATHINFO_BASENAME); } /** @@ -488,15 +525,12 @@ public function setElement(AttachmentContainingDBElement $element): self } /** - * Sets the filepath (with relative placeholder) for this attachment. - * - * @param string $path the new filepath of the attachment - * - * @return Attachment + * Sets the path to a file hosted internally. If you set this path to a file that was not downloaded from the + * external source in external_path, make sure to reset external_path. */ - public function setPath(string $path): self + public function setInternalPath(?string $internal_path): self { - $this->path = $path; + $this->internal_path = $internal_path; return $this; } @@ -512,35 +546,61 @@ public function setAttachmentType(AttachmentType $attachement_type): self } /** - * Sets the url associated with this attachment. - * If the url is empty nothing is changed, to not override the file path. - * - * @return Attachment + * Sets up the paths using a user provided string which might contain an external path or a builtin path. Allows + * resetting the external path if an internal path exists. Resets any other paths if a (nonempty) new path is set. */ #[Groups(['attachment:write'])] #[SerializedName('url')] + #[ApiProperty(description: 'Set the path of the attachment here. + Provide either an external URL, a path to a builtin file (like %FOOTPRINTS%/Active/ICs/IC_DFS.png) or an empty + string if the attachment has an internal file associated and you\'d like to reset the external source. + If you set a new (nonempty) file path any associated internal file will be removed!')] public function setURL(?string $url): self { - //Do nothing if the URL is empty - if ($url === null || $url === '') { + //Don't allow the user to set an empty external path if the internal path is empty already + if (($url === null || $url === "") && !$this->hasInternal()) { return $this; } - $url = trim($url); - //Escape spaces in URL - $url = str_replace(' ', '%20', $url); + //The URL field can also contain the special builtin internal paths, so we need to distinguish here + if ($this::checkIfBuiltin($url)) { + $this->setInternalPath($url); + //make sure the external path isn't still pointing to something unrelated + $this->setExternalPath(null); + } else { + $this->setExternalPath($url); + } + return $this; + } - //Only set if the URL is not empty - if ($url !== '') { - if (str_contains($url, '%BASE%') || str_contains($url, '%MEDIA%')) { - throw new InvalidArgumentException('You can not reference internal files via the url field! But nice try!'); - } - $this->path = $url; - //Reset internal filename - $this->original_filename = null; + /** + * Sets the path to a file hosted on an external server. Setting the external path to a (nonempty) value different + * from the the old one _clears_ the internal path, so that the external path reflects where any associated internal + * file came from. + */ + public function setExternalPath(?string $external_path): self + { + //If we only clear the external path, don't reset the internal path, since that could be confusing + if($external_path === null || $external_path === '') { + $this->external_path = null; + return $this; } + $external_path = trim($external_path); + //Escape spaces in URL + $external_path = str_replace(' ', '%20', $external_path); + + if($this->external_path === $external_path) { + //Nothing changed, nothing to do + return $this; + } + + $this->external_path = $external_path; + $this->internal_path = null; + //Reset internal filename + $this->original_filename = null; + return $this; } @@ -551,12 +611,17 @@ public function setURL(?string $url): self /** * Checks if the given path is a path to a builtin resource. * - * @param string $path The path that should be checked + * @param string|null $path The path that should be checked * * @return bool true if the path is pointing to a builtin resource */ - public static function checkIfBuiltin(string $path): bool + public static function checkIfBuiltin(?string $path): bool { + //An empty path can't be a builtin + if ($path === null) { + return false; + } + //After the %PLACEHOLDER% comes a slash, so we can check if we have a placeholder via explode $tmp = explode('/', $path); //Builtins must have a %PLACEHOLDER% construction diff --git a/src/Entity/Attachments/PartCustomStateAttachment.php b/src/Entity/Attachments/PartCustomStateAttachment.php new file mode 100644 index 000000000..3a561b136 --- /dev/null +++ b/src/Entity/Attachments/PartCustomStateAttachment.php @@ -0,0 +1,45 @@ +. + */ + +declare(strict_types=1); + +namespace App\Entity\Attachments; + +use App\Entity\Parts\PartCustomState; +use App\Serializer\APIPlatform\OverrideClassDenormalizer; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; +use Symfony\Component\Serializer\Attribute\Context; + +/** + * An attachment attached to a part custom state element. + * @extends Attachment + */ +#[UniqueEntity(['name', 'attachment_type', 'element'])] +#[ORM\Entity] +class PartCustomStateAttachment extends Attachment +{ + final public const ALLOWED_ELEMENT_CLASS = PartCustomState::class; + + #[ORM\ManyToOne(targetEntity: PartCustomState::class, inversedBy: 'attachments')] + #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] + #[Context(denormalizationContext: [OverrideClassDenormalizer::CONTEXT_KEY => self::ALLOWED_ELEMENT_CLASS])] + protected ?AttachmentContainingDBElement $element = null; +} diff --git a/src/Entity/Base/AbstractCompany.php b/src/Entity/Base/AbstractCompany.php index ccb74633b..57a3f7224 100644 --- a/src/Entity/Base/AbstractCompany.php +++ b/src/Entity/Base/AbstractCompany.php @@ -81,7 +81,7 @@ abstract class AbstractCompany extends AbstractPartsContainingDBElement /** * @var string The website of the company */ - #[Assert\Url] + #[Assert\Url(requireTld: false)] #[Groups(['full', 'company:read', 'company:write', 'import', 'extended'])] #[ORM\Column(type: Types::STRING)] #[Assert\Length(max: 255)] @@ -162,7 +162,7 @@ public function getWebsite(): string * * @return string the link to the article */ - public function getAutoProductUrl(string $partnr = null): string + public function getAutoProductUrl(?string $partnr = null): string { if (is_string($partnr)) { return str_replace('%PARTNUMBER%', $partnr, $this->auto_product_url); diff --git a/src/Entity/Base/AbstractDBElement.php b/src/Entity/Base/AbstractDBElement.php index 871a22d00..a088b3dfc 100644 --- a/src/Entity/Base/AbstractDBElement.php +++ b/src/Entity/Base/AbstractDBElement.php @@ -33,12 +33,15 @@ use App\Entity\Attachments\ManufacturerAttachment; use App\Entity\Attachments\MeasurementUnitAttachment; use App\Entity\Attachments\PartAttachment; +use App\Entity\Attachments\PartCustomStateAttachment; use App\Entity\Attachments\ProjectAttachment; use App\Entity\Attachments\StorageLocationAttachment; use App\Entity\Attachments\SupplierAttachment; use App\Entity\Attachments\UserAttachment; use App\Entity\Parameters\AbstractParameter; use App\Entity\Parts\Category; +use App\Entity\PriceInformations\Pricedetail; +use App\Entity\Parts\PartCustomState; use App\Entity\ProjectSystem\Project; use App\Entity\ProjectSystem\ProjectBOMEntry; use App\Entity\Parts\Footprint; @@ -67,7 +70,41 @@ * Every database table which are managed with this class (or a subclass of it) * must have the table row "id"!! The ID is the unique key to identify the elements. */ -#[DiscriminatorMap(typeProperty: 'type', mapping: ['attachment_type' => AttachmentType::class, 'attachment' => Attachment::class, 'attachment_type_attachment' => AttachmentTypeAttachment::class, 'category_attachment' => CategoryAttachment::class, 'currency_attachment' => CurrencyAttachment::class, 'footprint_attachment' => FootprintAttachment::class, 'group_attachment' => GroupAttachment::class, 'label_attachment' => LabelAttachment::class, 'manufacturer_attachment' => ManufacturerAttachment::class, 'measurement_unit_attachment' => MeasurementUnitAttachment::class, 'part_attachment' => PartAttachment::class, 'project_attachment' => ProjectAttachment::class, 'storelocation_attachment' => StorageLocationAttachment::class, 'supplier_attachment' => SupplierAttachment::class, 'user_attachment' => UserAttachment::class, 'category' => Category::class, 'project' => Project::class, 'project_bom_entry' => ProjectBOMEntry::class, 'footprint' => Footprint::class, 'group' => Group::class, 'manufacturer' => Manufacturer::class, 'orderdetail' => Orderdetail::class, 'part' => Part::class, 'pricedetail' => 'App\Entity\PriceInformation\Pricedetail', 'storelocation' => StorageLocation::class, 'part_lot' => PartLot::class, 'currency' => Currency::class, 'measurement_unit' => MeasurementUnit::class, 'parameter' => AbstractParameter::class, 'supplier' => Supplier::class, 'user' => User::class])] +#[DiscriminatorMap(typeProperty: 'type', mapping: [ + 'attachment_type' => AttachmentType::class, + 'attachment' => Attachment::class, + 'attachment_type_attachment' => AttachmentTypeAttachment::class, + 'category_attachment' => CategoryAttachment::class, + 'currency_attachment' => CurrencyAttachment::class, + 'footprint_attachment' => FootprintAttachment::class, + 'group_attachment' => GroupAttachment::class, + 'label_attachment' => LabelAttachment::class, + 'manufacturer_attachment' => ManufacturerAttachment::class, + 'measurement_unit_attachment' => MeasurementUnitAttachment::class, + 'part_attachment' => PartAttachment::class, + 'part_custom_state_attachment' => PartCustomStateAttachment::class, + 'project_attachment' => ProjectAttachment::class, + 'storelocation_attachment' => StorageLocationAttachment::class, + 'supplier_attachment' => SupplierAttachment::class, + 'user_attachment' => UserAttachment::class, + 'category' => Category::class, + 'project' => Project::class, + 'project_bom_entry' => ProjectBOMEntry::class, + 'footprint' => Footprint::class, + 'group' => Group::class, + 'manufacturer' => Manufacturer::class, + 'orderdetail' => Orderdetail::class, + 'part' => Part::class, + 'part_custom_state' => PartCustomState::class, + 'pricedetail' => Pricedetail::class, + 'storelocation' => StorageLocation::class, + 'part_lot' => PartLot::class, + 'currency' => Currency::class, + 'measurement_unit' => MeasurementUnit::class, + 'parameter' => AbstractParameter::class, + 'supplier' => Supplier::class, + 'user' => User::class] +)] #[ORM\MappedSuperclass(repositoryClass: DBElementRepository::class)] abstract class AbstractDBElement implements JsonSerializable { diff --git a/src/Entity/Base/AbstractStructuralDBElement.php b/src/Entity/Base/AbstractStructuralDBElement.php index f1cab493f..660710db1 100644 --- a/src/Entity/Base/AbstractStructuralDBElement.php +++ b/src/Entity/Base/AbstractStructuralDBElement.php @@ -318,6 +318,7 @@ public function getSubelements(): iterable return new ArrayCollection(); } + //@phpstan-ignore-next-line return $this->children ?? new ArrayCollection(); } diff --git a/src/Entity/InfoProviderSystem/BulkImportJobStatus.php b/src/Entity/InfoProviderSystem/BulkImportJobStatus.php new file mode 100644 index 000000000..7a88802f4 --- /dev/null +++ b/src/Entity/InfoProviderSystem/BulkImportJobStatus.php @@ -0,0 +1,35 @@ +. + */ + +declare(strict_types=1); + +namespace App\Entity\InfoProviderSystem; + +use Symfony\Contracts\Translation\TranslatableInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +enum BulkImportJobStatus: string +{ + case PENDING = 'pending'; + case IN_PROGRESS = 'in_progress'; + case COMPLETED = 'completed'; + case STOPPED = 'stopped'; + case FAILED = 'failed'; +} diff --git a/src/Entity/InfoProviderSystem/BulkImportPartStatus.php b/src/Entity/InfoProviderSystem/BulkImportPartStatus.php new file mode 100644 index 000000000..0eedc553b --- /dev/null +++ b/src/Entity/InfoProviderSystem/BulkImportPartStatus.php @@ -0,0 +1,32 @@ +. + */ + +declare(strict_types=1); + +namespace App\Entity\InfoProviderSystem; + + +enum BulkImportPartStatus: string +{ + case PENDING = 'pending'; + case COMPLETED = 'completed'; + case SKIPPED = 'skipped'; + case FAILED = 'failed'; +} diff --git a/src/Entity/InfoProviderSystem/BulkInfoProviderImportJob.php b/src/Entity/InfoProviderSystem/BulkInfoProviderImportJob.php new file mode 100644 index 000000000..bc842a262 --- /dev/null +++ b/src/Entity/InfoProviderSystem/BulkInfoProviderImportJob.php @@ -0,0 +1,449 @@ +. + */ + +declare(strict_types=1); + +namespace App\Entity\InfoProviderSystem; + +use App\Entity\Base\AbstractDBElement; +use App\Entity\Parts\Part; +use App\Entity\UserSystem\User; +use App\Services\InfoProviderSystem\DTOs\BulkSearchFieldMappingDTO; +use App\Services\InfoProviderSystem\DTOs\BulkSearchResponseDTO; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Mapping as ORM; + +#[ORM\Entity] +#[ORM\Table(name: 'bulk_info_provider_import_jobs')] +class BulkInfoProviderImportJob extends AbstractDBElement +{ + #[ORM\Column(type: Types::TEXT)] + private string $name = ''; + + #[ORM\Column(type: Types::JSON)] + private array $fieldMappings = []; + + /** + * @var BulkSearchFieldMappingDTO[] The deserialized field mappings DTOs, cached for performance + */ + private ?array $fieldMappingsDTO = null; + + #[ORM\Column(type: Types::JSON)] + private array $searchResults = []; + + /** + * @var BulkSearchResponseDTO|null The deserialized search results DTO, cached for performance + */ + private ?BulkSearchResponseDTO $searchResultsDTO = null; + + #[ORM\Column(type: Types::STRING, length: 20, enumType: BulkImportJobStatus::class)] + private BulkImportJobStatus $status = BulkImportJobStatus::PENDING; + + #[ORM\Column(type: Types::DATETIME_IMMUTABLE)] + private \DateTimeImmutable $createdAt; + + #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)] + private ?\DateTimeImmutable $completedAt = null; + + #[ORM\Column(type: Types::BOOLEAN)] + private bool $prefetchDetails = false; + + #[ORM\ManyToOne(targetEntity: User::class)] + #[ORM\JoinColumn(nullable: false)] + private ?User $createdBy = null; + + /** @var Collection */ + #[ORM\OneToMany(targetEntity: BulkInfoProviderImportJobPart::class, mappedBy: 'job', cascade: ['persist', 'remove'], orphanRemoval: true)] + private Collection $jobParts; + + public function __construct() + { + $this->createdAt = new \DateTimeImmutable(); + $this->jobParts = new ArrayCollection(); + } + + public function getName(): string + { + return $this->name; + } + + public function getDisplayNameKey(): string + { + return 'info_providers.bulk_import.job_name_template'; + } + + public function getDisplayNameParams(): array + { + return ['%count%' => $this->getPartCount()]; + } + + public function getFormattedTimestamp(): string + { + return $this->createdAt->format('Y-m-d H:i:s'); + } + + public function setName(string $name): self + { + $this->name = $name; + return $this; + } + + public function getJobParts(): Collection + { + return $this->jobParts; + } + + public function addJobPart(BulkInfoProviderImportJobPart $jobPart): self + { + if (!$this->jobParts->contains($jobPart)) { + $this->jobParts->add($jobPart); + $jobPart->setJob($this); + } + return $this; + } + + public function removeJobPart(BulkInfoProviderImportJobPart $jobPart): self + { + if ($this->jobParts->removeElement($jobPart)) { + if ($jobPart->getJob() === $this) { + $jobPart->setJob(null); + } + } + return $this; + } + + public function getPartIds(): array + { + return $this->jobParts->map(fn($jobPart) => $jobPart->getPart()->getId())->toArray(); + } + + public function setPartIds(array $partIds): self + { + // This method is kept for backward compatibility but should be replaced with addJobPart + // Clear existing job parts + $this->jobParts->clear(); + + // Add new job parts (this would need the actual Part entities, not just IDs) + // This is a simplified implementation - in practice, you'd want to pass Part entities + return $this; + } + + public function addPart(Part $part): self + { + $jobPart = new BulkInfoProviderImportJobPart($this, $part); + $this->addJobPart($jobPart); + return $this; + } + + /** + * @return BulkSearchFieldMappingDTO[] The deserialized field mappings + */ + public function getFieldMappings(): array + { + if ($this->fieldMappingsDTO === null) { + // Lazy load the DTOs from the raw JSON data + $this->fieldMappingsDTO = array_map( + static fn($data) => BulkSearchFieldMappingDTO::fromSerializableArray($data), + $this->fieldMappings + ); + } + + return $this->fieldMappingsDTO; + } + + /** + * @param BulkSearchFieldMappingDTO[] $fieldMappings + * @return $this + */ + public function setFieldMappings(array $fieldMappings): self + { + //Ensure that we are dealing with the objects here + if (count($fieldMappings) > 0 && !$fieldMappings[0] instanceof BulkSearchFieldMappingDTO) { + throw new \InvalidArgumentException('Expected an array of FieldMappingDTO objects'); + } + + $this->fieldMappingsDTO = $fieldMappings; + + $this->fieldMappings = array_map( + static fn(BulkSearchFieldMappingDTO $dto) => $dto->toSerializableArray(), + $fieldMappings + ); + return $this; + } + + public function getSearchResultsRaw(): array + { + return $this->searchResults; + } + + public function setSearchResultsRaw(array $searchResults): self + { + $this->searchResults = $searchResults; + return $this; + } + + public function setSearchResults(BulkSearchResponseDTO $searchResponse): self + { + $this->searchResultsDTO = $searchResponse; + $this->searchResults = $searchResponse->toSerializableRepresentation(); + return $this; + } + + public function getSearchResults(EntityManagerInterface $entityManager): BulkSearchResponseDTO + { + if ($this->searchResultsDTO === null) { + // Lazy load the DTO from the raw JSON data + $this->searchResultsDTO = BulkSearchResponseDTO::fromSerializableRepresentation($this->searchResults, $entityManager); + } + return $this->searchResultsDTO; + } + + public function hasSearchResults(): bool + { + return !empty($this->searchResults); + } + + public function getStatus(): BulkImportJobStatus + { + return $this->status; + } + + public function setStatus(BulkImportJobStatus $status): self + { + $this->status = $status; + return $this; + } + + public function getCreatedAt(): \DateTimeImmutable + { + return $this->createdAt; + } + + public function getCompletedAt(): ?\DateTimeImmutable + { + return $this->completedAt; + } + + public function setCompletedAt(?\DateTimeImmutable $completedAt): self + { + $this->completedAt = $completedAt; + return $this; + } + + public function isPrefetchDetails(): bool + { + return $this->prefetchDetails; + } + + public function setPrefetchDetails(bool $prefetchDetails): self + { + $this->prefetchDetails = $prefetchDetails; + return $this; + } + + public function getCreatedBy(): User + { + return $this->createdBy; + } + + public function setCreatedBy(User $createdBy): self + { + $this->createdBy = $createdBy; + return $this; + } + + public function getProgress(): array + { + $progress = []; + foreach ($this->jobParts as $jobPart) { + $progressData = [ + 'status' => $jobPart->getStatus()->value + ]; + + // Only include completed_at if it's not null + if ($jobPart->getCompletedAt() !== null) { + $progressData['completed_at'] = $jobPart->getCompletedAt()->format('c'); + } + + // Only include reason if it's not null + if ($jobPart->getReason() !== null) { + $progressData['reason'] = $jobPart->getReason(); + } + + $progress[$jobPart->getPart()->getId()] = $progressData; + } + return $progress; + } + + public function markAsCompleted(): self + { + $this->status = BulkImportJobStatus::COMPLETED; + $this->completedAt = new \DateTimeImmutable(); + return $this; + } + + public function markAsFailed(): self + { + $this->status = BulkImportJobStatus::FAILED; + $this->completedAt = new \DateTimeImmutable(); + return $this; + } + + public function markAsStopped(): self + { + $this->status = BulkImportJobStatus::STOPPED; + $this->completedAt = new \DateTimeImmutable(); + return $this; + } + + public function markAsInProgress(): self + { + $this->status = BulkImportJobStatus::IN_PROGRESS; + return $this; + } + + public function isPending(): bool + { + return $this->status === BulkImportJobStatus::PENDING; + } + + public function isInProgress(): bool + { + return $this->status === BulkImportJobStatus::IN_PROGRESS; + } + + public function isCompleted(): bool + { + return $this->status === BulkImportJobStatus::COMPLETED; + } + + public function isFailed(): bool + { + return $this->status === BulkImportJobStatus::FAILED; + } + + public function isStopped(): bool + { + return $this->status === BulkImportJobStatus::STOPPED; + } + + public function canBeStopped(): bool + { + return $this->status === BulkImportJobStatus::PENDING || $this->status === BulkImportJobStatus::IN_PROGRESS; + } + + public function getPartCount(): int + { + return $this->jobParts->count(); + } + + public function getResultCount(): int + { + $count = 0; + foreach ($this->searchResults as $partResult) { + $count += count($partResult['search_results'] ?? []); + } + return $count; + } + + public function markPartAsCompleted(int $partId): self + { + $jobPart = $this->findJobPartByPartId($partId); + if ($jobPart) { + $jobPart->markAsCompleted(); + } + return $this; + } + + public function markPartAsSkipped(int $partId, string $reason = ''): self + { + $jobPart = $this->findJobPartByPartId($partId); + if ($jobPart) { + $jobPart->markAsSkipped($reason); + } + return $this; + } + + public function markPartAsPending(int $partId): self + { + $jobPart = $this->findJobPartByPartId($partId); + if ($jobPart) { + $jobPart->markAsPending(); + } + return $this; + } + + public function isPartCompleted(int $partId): bool + { + $jobPart = $this->findJobPartByPartId($partId); + return $jobPart ? $jobPart->isCompleted() : false; + } + + public function isPartSkipped(int $partId): bool + { + $jobPart = $this->findJobPartByPartId($partId); + return $jobPart ? $jobPart->isSkipped() : false; + } + + public function getCompletedPartsCount(): int + { + return $this->jobParts->filter(fn($jobPart) => $jobPart->isCompleted())->count(); + } + + public function getSkippedPartsCount(): int + { + return $this->jobParts->filter(fn($jobPart) => $jobPart->isSkipped())->count(); + } + + private function findJobPartByPartId(int $partId): ?BulkInfoProviderImportJobPart + { + foreach ($this->jobParts as $jobPart) { + if ($jobPart->getPart()->getId() === $partId) { + return $jobPart; + } + } + return null; + } + + public function getProgressPercentage(): float + { + $total = $this->getPartCount(); + if ($total === 0) { + return 100.0; + } + + $completed = $this->getCompletedPartsCount() + $this->getSkippedPartsCount(); + return round(($completed / $total) * 100, 1); + } + + public function isAllPartsCompleted(): bool + { + $total = $this->getPartCount(); + if ($total === 0) { + return true; + } + + $completed = $this->getCompletedPartsCount() + $this->getSkippedPartsCount(); + return $completed >= $total; + } +} diff --git a/src/Entity/InfoProviderSystem/BulkInfoProviderImportJobPart.php b/src/Entity/InfoProviderSystem/BulkInfoProviderImportJobPart.php new file mode 100644 index 000000000..90519561d --- /dev/null +++ b/src/Entity/InfoProviderSystem/BulkInfoProviderImportJobPart.php @@ -0,0 +1,182 @@ +. + */ + +declare(strict_types=1); + +/* + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2023 Jan Bรถhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace App\Entity\InfoProviderSystem; + +use App\Entity\Base\AbstractDBElement; +use App\Entity\Parts\Part; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\Mapping as ORM; + +#[ORM\Entity] +#[ORM\Table(name: 'bulk_info_provider_import_job_parts')] +#[ORM\UniqueConstraint(name: 'unique_job_part', columns: ['job_id', 'part_id'])] +class BulkInfoProviderImportJobPart extends AbstractDBElement +{ + #[ORM\ManyToOne(targetEntity: BulkInfoProviderImportJob::class, inversedBy: 'jobParts')] + #[ORM\JoinColumn(nullable: false)] + private BulkInfoProviderImportJob $job; + + #[ORM\ManyToOne(targetEntity: Part::class, inversedBy: 'bulkImportJobParts')] + #[ORM\JoinColumn(nullable: false)] + private Part $part; + + #[ORM\Column(type: Types::STRING, length: 20, enumType: BulkImportPartStatus::class)] + private BulkImportPartStatus $status = BulkImportPartStatus::PENDING; + + #[ORM\Column(type: Types::TEXT, nullable: true)] + private ?string $reason = null; + + #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)] + private ?\DateTimeImmutable $completedAt = null; + + public function __construct(BulkInfoProviderImportJob $job, Part $part) + { + $this->job = $job; + $this->part = $part; + } + + public function getJob(): BulkInfoProviderImportJob + { + return $this->job; + } + + public function setJob(?BulkInfoProviderImportJob $job): self + { + $this->job = $job; + return $this; + } + + public function getPart(): Part + { + return $this->part; + } + + public function setPart(?Part $part): self + { + $this->part = $part; + return $this; + } + + public function getStatus(): BulkImportPartStatus + { + return $this->status; + } + + public function setStatus(BulkImportPartStatus $status): self + { + $this->status = $status; + return $this; + } + + public function getReason(): ?string + { + return $this->reason; + } + + public function setReason(?string $reason): self + { + $this->reason = $reason; + return $this; + } + + public function getCompletedAt(): ?\DateTimeImmutable + { + return $this->completedAt; + } + + public function setCompletedAt(?\DateTimeImmutable $completedAt): self + { + $this->completedAt = $completedAt; + return $this; + } + + public function markAsCompleted(): self + { + $this->status = BulkImportPartStatus::COMPLETED; + $this->completedAt = new \DateTimeImmutable(); + return $this; + } + + public function markAsSkipped(string $reason = ''): self + { + $this->status = BulkImportPartStatus::SKIPPED; + $this->reason = $reason; + $this->completedAt = new \DateTimeImmutable(); + return $this; + } + + public function markAsFailed(string $reason = ''): self + { + $this->status = BulkImportPartStatus::FAILED; + $this->reason = $reason; + $this->completedAt = new \DateTimeImmutable(); + return $this; + } + + public function markAsPending(): self + { + $this->status = BulkImportPartStatus::PENDING; + $this->reason = null; + $this->completedAt = null; + return $this; + } + + public function isPending(): bool + { + return $this->status === BulkImportPartStatus::PENDING; + } + + public function isCompleted(): bool + { + return $this->status === BulkImportPartStatus::COMPLETED; + } + + public function isSkipped(): bool + { + return $this->status === BulkImportPartStatus::SKIPPED; + } + + public function isFailed(): bool + { + return $this->status === BulkImportPartStatus::FAILED; + } +} diff --git a/src/Entity/LogSystem/CollectionElementDeleted.php b/src/Entity/LogSystem/CollectionElementDeleted.php index 16bf33f59..34ab8fba8 100644 --- a/src/Entity/LogSystem/CollectionElementDeleted.php +++ b/src/Entity/LogSystem/CollectionElementDeleted.php @@ -46,6 +46,7 @@ use App\Entity\Attachments\AttachmentTypeAttachment; use App\Entity\Attachments\CategoryAttachment; use App\Entity\Attachments\CurrencyAttachment; +use App\Entity\Attachments\PartCustomStateAttachment; use App\Entity\Attachments\ProjectAttachment; use App\Entity\Attachments\FootprintAttachment; use App\Entity\Attachments\GroupAttachment; @@ -58,6 +59,8 @@ use App\Entity\Base\AbstractDBElement; use App\Entity\Contracts\LogWithEventUndoInterface; use App\Entity\Contracts\NamedElementInterface; +use App\Entity\Parameters\PartCustomStateParameter; +use App\Entity\Parts\PartCustomState; use App\Entity\ProjectSystem\Project; use App\Entity\Parameters\AbstractParameter; use App\Entity\Parameters\AttachmentTypeParameter; @@ -158,6 +161,7 @@ private function resolveAbstractClassToInstantiableClass(string $abstract_class) Part::class => PartParameter::class, StorageLocation::class => StorageLocationParameter::class, Supplier::class => SupplierParameter::class, + PartCustomState::class => PartCustomStateParameter::class, default => throw new \RuntimeException('Unknown target class for parameter: '.$this->getTargetClass()), }; } @@ -173,6 +177,7 @@ private function resolveAbstractClassToInstantiableClass(string $abstract_class) Manufacturer::class => ManufacturerAttachment::class, MeasurementUnit::class => MeasurementUnitAttachment::class, Part::class => PartAttachment::class, + PartCustomState::class => PartCustomStateAttachment::class, StorageLocation::class => StorageLocationAttachment::class, Supplier::class => SupplierAttachment::class, User::class => UserAttachment::class, diff --git a/src/Entity/LogSystem/LogTargetType.php b/src/Entity/LogSystem/LogTargetType.php index 1c6e4f8c0..3b2d8682a 100644 --- a/src/Entity/LogSystem/LogTargetType.php +++ b/src/Entity/LogSystem/LogTargetType.php @@ -24,6 +24,8 @@ use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentType; +use App\Entity\InfoProviderSystem\BulkInfoProviderImportJob; +use App\Entity\InfoProviderSystem\BulkInfoProviderImportJobPart; use App\Entity\LabelSystem\LabelProfile; use App\Entity\Parameters\AbstractParameter; use App\Entity\Parts\Category; @@ -32,6 +34,7 @@ use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; use App\Entity\Parts\PartAssociation; +use App\Entity\Parts\PartCustomState; use App\Entity\Parts\PartLot; use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; @@ -67,6 +70,9 @@ enum LogTargetType: int case LABEL_PROFILE = 19; case PART_ASSOCIATION = 20; + case BULK_INFO_PROVIDER_IMPORT_JOB = 21; + case BULK_INFO_PROVIDER_IMPORT_JOB_PART = 22; + case PART_CUSTOM_STATE = 23; /** * Returns the class name of the target type or null if the target type is NONE. @@ -96,6 +102,9 @@ public function toClass(): ?string self::PARAMETER => AbstractParameter::class, self::LABEL_PROFILE => LabelProfile::class, self::PART_ASSOCIATION => PartAssociation::class, + self::BULK_INFO_PROVIDER_IMPORT_JOB => BulkInfoProviderImportJob::class, + self::BULK_INFO_PROVIDER_IMPORT_JOB_PART => BulkInfoProviderImportJobPart::class, + self::PART_CUSTOM_STATE => PartCustomState::class }; } diff --git a/src/Entity/OAuthToken.php b/src/Entity/OAuthToken.php index 0073aeed7..bc692369a 100644 --- a/src/Entity/OAuthToken.php +++ b/src/Entity/OAuthToken.php @@ -54,7 +54,7 @@ class OAuthToken extends AbstractNamedDBElement implements AccessTokenInterface */ private const DEFAULT_EXPIRATION_TIME = 3600; - public function __construct(string $name, ?string $refresh_token, ?string $token = null, \DateTimeImmutable $expires_at = null) + public function __construct(string $name, ?string $refresh_token, ?string $token = null, ?\DateTimeImmutable $expires_at = null) { //If token is given, you also have to give the expires_at date if ($token !== null && $expires_at === null) { diff --git a/src/Entity/Parameters/AbstractParameter.php b/src/Entity/Parameters/AbstractParameter.php index c42682f97..388745d49 100644 --- a/src/Entity/Parameters/AbstractParameter.php +++ b/src/Entity/Parameters/AbstractParameter.php @@ -73,7 +73,8 @@ #[ORM\DiscriminatorMap([0 => CategoryParameter::class, 1 => CurrencyParameter::class, 2 => ProjectParameter::class, 3 => FootprintParameter::class, 4 => GroupParameter::class, 5 => ManufacturerParameter::class, 6 => MeasurementUnitParameter::class, 7 => PartParameter::class, 8 => StorageLocationParameter::class, - 9 => SupplierParameter::class, 10 => AttachmentTypeParameter::class])] + 9 => SupplierParameter::class, 10 => AttachmentTypeParameter::class, + 12 => PartCustomStateParameter::class])] #[ORM\Table('parameters')] #[ORM\Index(columns: ['name'], name: 'parameter_name_idx')] #[ORM\Index(columns: ['param_group'], name: 'parameter_group_idx')] @@ -105,7 +106,7 @@ abstract class AbstractParameter extends AbstractNamedDBElement implements Uniqu "AttachmentType" => AttachmentTypeParameter::class, "Category" => CategoryParameter::class, "Currency" => CurrencyParameter::class, "Project" => ProjectParameter::class, "Footprint" => FootprintParameter::class, "Group" => GroupParameter::class, "Manufacturer" => ManufacturerParameter::class, "MeasurementUnit" => MeasurementUnitParameter::class, - "StorageLocation" => StorageLocationParameter::class, "Supplier" => SupplierParameter::class]; + "StorageLocation" => StorageLocationParameter::class, "Supplier" => SupplierParameter::class, "PartCustomState" => PartCustomStateParameter::class]; /** * @var string The class of the element that can be passed to this attachment. Must be overridden in subclasses. @@ -208,7 +209,7 @@ public function getElement(): ?AbstractDBElement */ #[Groups(['parameter:read', 'full'])] #[SerializedName('formatted')] - public function getFormattedValue(): string + public function getFormattedValue(bool $latex_formatted = false): string { //If we just only have text value, return early if (null === $this->value_typical && null === $this->value_min && null === $this->value_max) { @@ -217,20 +218,20 @@ public function getFormattedValue(): string $str = ''; $bracket_opened = false; - if ($this->value_typical) { - $str .= $this->getValueTypicalWithUnit(); + if ($this->value_typical !== null) { + $str .= $this->getValueTypicalWithUnit($latex_formatted); if ($this->value_min || $this->value_max) { $bracket_opened = true; $str .= ' ('; } } - if ($this->value_max && $this->value_min) { - $str .= $this->getValueMinWithUnit().' ... '.$this->getValueMaxWithUnit(); - } elseif ($this->value_max) { - $str .= 'max. '.$this->getValueMaxWithUnit(); - } elseif ($this->value_min) { - $str .= 'min. '.$this->getValueMinWithUnit(); + if ($this->value_max !== null && $this->value_min !== null) { + $str .= $this->getValueMinWithUnit($latex_formatted).' ... '.$this->getValueMaxWithUnit($latex_formatted); + } elseif ($this->value_max !== null) { + $str .= 'max. '.$this->getValueMaxWithUnit($latex_formatted); + } elseif ($this->value_min !== null) { + $str .= 'min. '.$this->getValueMinWithUnit($latex_formatted); } //Add closing bracket @@ -344,25 +345,25 @@ public function getValueTypical(): ?float /** * Return a formatted version with the minimum value with the unit of this parameter. */ - public function getValueTypicalWithUnit(): string + public function getValueTypicalWithUnit(bool $with_latex = false): string { - return $this->formatWithUnit($this->value_typical); + return $this->formatWithUnit($this->value_typical, with_latex: $with_latex); } /** * Return a formatted version with the maximum value with the unit of this parameter. */ - public function getValueMaxWithUnit(): string + public function getValueMaxWithUnit(bool $with_latex = false): string { - return $this->formatWithUnit($this->value_max); + return $this->formatWithUnit($this->value_max, with_latex: $with_latex); } /** * Return a formatted version with the typical value with the unit of this parameter. */ - public function getValueMinWithUnit(): string + public function getValueMinWithUnit(bool $with_latex = false): string { - return $this->formatWithUnit($this->value_min); + return $this->formatWithUnit($this->value_min, with_latex: $with_latex); } /** @@ -441,16 +442,26 @@ public function setValueText(string $value_text): self /** * Return a string representation and (if possible) with its unit. */ - protected function formatWithUnit(float $value, string $format = '%g'): string + protected function formatWithUnit(float $value, string $format = '%g', bool $with_latex = false): string { $str = sprintf($format, $value); if ($this->unit !== '') { - return $str.' '.$this->unit; + + if (!$with_latex) { + $unit = $this->unit; + } else { + //Escape the percentage sign for convenience (as latex uses it as comment and it is often used in units) + $escaped = preg_replace('/\\\\?%/', "\\\\%", $this->unit); + + $unit = '$\mathrm{'.$escaped.'}$'; + } + + return $str.' '.$unit; } return $str; } - + /** * Returns the class of the element that is allowed to be associated with this attachment. * @return string diff --git a/src/Entity/Parameters/PartCustomStateParameter.php b/src/Entity/Parameters/PartCustomStateParameter.php new file mode 100644 index 000000000..ceedf7b4d --- /dev/null +++ b/src/Entity/Parameters/PartCustomStateParameter.php @@ -0,0 +1,65 @@ +. + */ + +declare(strict_types=1); + +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2022 Jan Bรถhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace App\Entity\Parameters; + +use App\Entity\Base\AbstractDBElement; +use App\Entity\Parts\PartCustomState; +use App\Repository\ParameterRepository; +use App\Serializer\APIPlatform\OverrideClassDenormalizer; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; +use Symfony\Component\Serializer\Attribute\Context; + +#[UniqueEntity(fields: ['name', 'group', 'element'])] +#[ORM\Entity(repositoryClass: ParameterRepository::class)] +class PartCustomStateParameter extends AbstractParameter +{ + final public const ALLOWED_ELEMENT_CLASS = PartCustomState::class; + + /** + * @var PartCustomState the element this para is associated with + */ + #[ORM\ManyToOne(targetEntity: PartCustomState::class, inversedBy: 'parameters')] + #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] + #[Context(denormalizationContext: [OverrideClassDenormalizer::CONTEXT_KEY => self::ALLOWED_ELEMENT_CLASS])] + protected ?AbstractDBElement $element = null; +} diff --git a/src/Entity/Parts/Part.php b/src/Entity/Parts/Part.php index 14a7903fc..f1dd6040a 100644 --- a/src/Entity/Parts/Part.php +++ b/src/Entity/Parts/Part.php @@ -22,8 +22,6 @@ 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; @@ -40,10 +38,12 @@ use App\ApiPlatform\Filter\EntityFilter; use App\ApiPlatform\Filter\LikeFilter; use App\ApiPlatform\Filter\PartStoragelocationFilter; +use App\ApiPlatform\Filter\TagFilter; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentContainingDBElement; use App\Entity\Attachments\PartAttachment; use App\Entity\EDA\EDAPartInfo; +use App\Entity\InfoProviderSystem\BulkInfoProviderImportJobPart; use App\Entity\Parameters\ParametersTrait; use App\Entity\Parameters\PartParameter; use App\Entity\Parts\PartTraits\AdvancedPropertyTrait; @@ -59,6 +59,7 @@ use App\Validator\Constraints\UniqueObjectCollection; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; +use Doctrine\Common\Collections\Criteria; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Serializer\Annotation\Groups; @@ -83,8 +84,18 @@ #[ORM\Index(columns: ['ipn'], name: 'parts_idx_ipn')] #[ApiResource( operations: [ - new Get(normalizationContext: ['groups' => ['part:read', 'provider_reference:read', 'api:basic:read', 'part_lot:read', - 'orderdetail:read', 'pricedetail:read', 'parameter:read', 'attachment:read', 'eda_info:read'], + new Get(normalizationContext: [ + 'groups' => [ + 'part:read', + 'provider_reference:read', + 'api:basic:read', + 'part_lot:read', + 'orderdetail:read', + 'pricedetail:read', + 'parameter:read', + 'attachment:read', + 'eda_info:read' + ], 'openapi_definition_name' => 'Read', ], security: 'is_granted("read", object)'), new GetCollection(security: 'is_granted("@parts.read")'), @@ -92,15 +103,15 @@ new Patch(security: 'is_granted("edit", object)'), new Delete(security: 'is_granted("delete", object)'), ], - normalizationContext: ['groups' => ['part:read', 'provider_reference:read', 'api:basic:read', 'part_lot:read'], 'openapi_definition_name' => 'Read'], + normalizationContext: ['groups' => ['part:read', 'provider_reference:read', 'api:basic:read', 'part_lot:read'], 'openapi_definition_name' => 'Read'], denormalizationContext: ['groups' => ['part:write', 'api:basic:write', 'eda_info:write', 'attachment:write', 'parameter:write'], 'openapi_definition_name' => 'Write'], )] #[ApiFilter(PropertyFilter::class)] -#[ApiFilter(EntityFilter::class, properties: ["category", "footprint", "manufacturer", "partUnit"])] +#[ApiFilter(EntityFilter::class, properties: ["category", "footprint", "manufacturer", "partUnit", "partCustomState"])] #[ApiFilter(PartStoragelocationFilter::class, properties: ["storage_location"])] #[ApiFilter(LikeFilter::class, properties: ["name", "comment", "description", "ipn", "manufacturer_product_number"])] #[ApiFilter(TagFilter::class, properties: ["tags"])] -#[ApiFilter(BooleanFilter::class, properties: ["favorite" , "needs_review"])] +#[ApiFilter(BooleanFilter::class, properties: ["favorite", "needs_review"])] #[ApiFilter(RangeFilter::class, properties: ["mass", "minamount"])] #[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)] #[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] @@ -160,6 +171,12 @@ class Part extends AttachmentContainingDBElement #[Groups(['part:read'])] protected ?\DateTimeImmutable $lastModified = null; + /** + * @var Collection + */ + #[ORM\OneToMany(mappedBy: 'part', targetEntity: BulkInfoProviderImportJobPart::class, cascade: ['remove'], orphanRemoval: true)] + protected Collection $bulkImportJobParts; + public function __construct() { @@ -172,6 +189,7 @@ public function __construct() $this->associated_parts_as_owner = new ArrayCollection(); $this->associated_parts_as_other = new ArrayCollection(); + $this->bulkImportJobParts = new ArrayCollection(); //By default, the part has no provider $this->providerReference = InfoProviderReference::noProvider(); @@ -230,4 +248,38 @@ public function validate(ExecutionContextInterface $context, $payload): void } } } + + /** + * Get all bulk import job parts for this part + * @return Collection + */ + public function getBulkImportJobParts(): Collection + { + return $this->bulkImportJobParts; + } + + /** + * Add a bulk import job part to this part + */ + public function addBulkImportJobPart(BulkInfoProviderImportJobPart $jobPart): self + { + if (!$this->bulkImportJobParts->contains($jobPart)) { + $this->bulkImportJobParts->add($jobPart); + $jobPart->setPart($this); + } + return $this; + } + + /** + * Remove a bulk import job part from this part + */ + public function removeBulkImportJobPart(BulkInfoProviderImportJobPart $jobPart): self + { + if ($this->bulkImportJobParts->removeElement($jobPart)) { + if ($jobPart->getPart() === $this) { + $jobPart->setPart(null); + } + } + return $this; + } } diff --git a/src/Entity/Parts/PartCustomState.php b/src/Entity/Parts/PartCustomState.php new file mode 100644 index 000000000..136ff9847 --- /dev/null +++ b/src/Entity/Parts/PartCustomState.php @@ -0,0 +1,127 @@ +. + */ + +declare(strict_types=1); + +namespace App\Entity\Parts; + +use ApiPlatform\Metadata\ApiProperty; +use App\Entity\Attachments\Attachment; +use App\Entity\Attachments\PartCustomStateAttachment; +use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Serializer\Filter\PropertyFilter; +use App\ApiPlatform\Filter\LikeFilter; +use App\Entity\Base\AbstractPartsContainingDBElement; +use App\Entity\Base\AbstractStructuralDBElement; +use App\Entity\Parameters\PartCustomStateParameter; +use App\Repository\Parts\PartCustomStateRepository; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\Common\Collections\Criteria; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Validator\Constraints as Assert; + +/** + * This entity represents a custom part state. + * If an organisation uses Part-DB and has its custom part states, this is useful. + * + * @extends AbstractPartsContainingDBElement + */ +#[ORM\Entity(repositoryClass: PartCustomStateRepository::class)] +#[ORM\Table('`part_custom_states`')] +#[ORM\Index(columns: ['name'], name: 'part_custom_state_name')] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@part_custom_states.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['part_custom_state:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['part_custom_state:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiFilter(PropertyFilter::class)] +#[ApiFilter(LikeFilter::class, properties: ["name"])] +#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)] +#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])] +class PartCustomState extends AbstractPartsContainingDBElement +{ + /** + * @var string The comment info for this element as markdown + */ + #[Groups(['part_custom_state:read', 'part_custom_state:write', 'full', 'import'])] + protected string $comment = ''; + + #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class, cascade: ['persist'])] + #[ORM\OrderBy(['name' => Criteria::ASC])] + protected Collection $children; + + #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] + #[ORM\JoinColumn(name: 'parent_id')] + #[Groups(['part_custom_state:read', 'part_custom_state:write'])] + #[ApiProperty(readableLink: false, writableLink: false)] + protected ?AbstractStructuralDBElement $parent = null; + + /** + * @var Collection + */ + #[Assert\Valid] + #[ORM\OneToMany(targetEntity: PartCustomStateAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['name' => Criteria::ASC])] + #[Groups(['part_custom_state:read', 'part_custom_state:write'])] + protected Collection $attachments; + + #[ORM\ManyToOne(targetEntity: PartCustomStateAttachment::class)] + #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] + #[Groups(['part_custom_state:read', 'part_custom_state:write'])] + protected ?Attachment $master_picture_attachment = null; + + /** @var Collection + */ + #[Assert\Valid] + #[ORM\OneToMany(mappedBy: 'element', targetEntity: PartCustomStateParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['name' => 'ASC'])] + #[Groups(['part_custom_state:read', 'part_custom_state:write'])] + protected Collection $parameters; + + #[Groups(['part_custom_state:read'])] + protected ?\DateTimeImmutable $addedDate = null; + #[Groups(['part_custom_state:read'])] + protected ?\DateTimeImmutable $lastModified = null; + + public function __construct() + { + parent::__construct(); + $this->children = new ArrayCollection(); + $this->attachments = new ArrayCollection(); + $this->parameters = new ArrayCollection(); + } +} diff --git a/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php b/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php index 230ba7b76..dd541d79a 100644 --- a/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php +++ b/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php @@ -23,6 +23,7 @@ namespace App\Entity\Parts\PartTraits; use App\Entity\Parts\InfoProviderReference; +use App\Entity\Parts\PartCustomState; use Doctrine\DBAL\Types\Types; use App\Entity\Parts\Part; use Doctrine\ORM\Mapping as ORM; @@ -73,6 +74,14 @@ trait AdvancedPropertyTrait #[Groups(['full', 'part:read'])] protected InfoProviderReference $providerReference; + /** + * @var ?PartCustomState the custom state for the part + */ + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] + #[ORM\ManyToOne(targetEntity: PartCustomState::class)] + #[ORM\JoinColumn(name: 'id_part_custom_state')] + protected ?PartCustomState $partCustomState = null; + /** * Checks if this part is marked, for that it needs further review. */ @@ -180,7 +189,24 @@ public function setProviderReference(InfoProviderReference $providerReference): return $this; } + /** + * Gets the custom part state for the part + * Returns null if no specific part state is set. + */ + public function getPartCustomState(): ?PartCustomState + { + return $this->partCustomState; + } + /** + * Sets the custom part state. + * + * @return $this + */ + public function setPartCustomState(?PartCustomState $partCustomState): self + { + $this->partCustomState = $partCustomState; - + return $this; + } } diff --git a/src/Entity/Parts/PartTraits/ManufacturerTrait.php b/src/Entity/Parts/PartTraits/ManufacturerTrait.php index 5d7f8749e..911a08062 100644 --- a/src/Entity/Parts/PartTraits/ManufacturerTrait.php +++ b/src/Entity/Parts/PartTraits/ManufacturerTrait.php @@ -49,7 +49,7 @@ trait ManufacturerTrait /** * @var string The url to the part on the manufacturer's homepage */ - #[Assert\Url] + #[Assert\Url(requireTld: false)] #[Groups(['full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::TEXT)] protected string $manufacturer_product_url = ''; diff --git a/src/Entity/Parts/PartTraits/ProjectTrait.php b/src/Entity/Parts/PartTraits/ProjectTrait.php index 457193770..7e1962d38 100644 --- a/src/Entity/Parts/PartTraits/ProjectTrait.php +++ b/src/Entity/Parts/PartTraits/ProjectTrait.php @@ -15,7 +15,7 @@ trait ProjectTrait /** * @var Collection $project_bom_entries */ - #[ORM\OneToMany(mappedBy: 'part', targetEntity: ProjectBOMEntry::class, cascade: ['remove'], orphanRemoval: true)] + #[ORM\OneToMany(targetEntity: ProjectBOMEntry::class, mappedBy: 'part')] protected Collection $project_bom_entries; /** diff --git a/src/Entity/PriceInformations/Orderdetail.php b/src/Entity/PriceInformations/Orderdetail.php index 3709b37d7..8ed76a46b 100644 --- a/src/Entity/PriceInformations/Orderdetail.php +++ b/src/Entity/PriceInformations/Orderdetail.php @@ -124,7 +124,7 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N /** * @var string The URL to the product on the supplier's website */ - #[Assert\Url] + #[Assert\Url(requireTld: false)] #[Groups(['full', 'import', 'orderdetail:read', 'orderdetail:write'])] #[ORM\Column(type: Types::TEXT)] protected string $supplier_product_url = ''; diff --git a/src/Entity/SettingsEntry.php b/src/Entity/SettingsEntry.php new file mode 100644 index 000000000..488de1d11 --- /dev/null +++ b/src/Entity/SettingsEntry.php @@ -0,0 +1,35 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Entity; + +use Doctrine\DBAL\Types\Types; +use Jbtronics\SettingsBundle\Entity\AbstractSettingsORMEntry; +use Doctrine\ORM\Mapping as ORM; + +#[ORM\Entity] +class SettingsEntry extends AbstractSettingsORMEntry +{ + #[ORM\Id, ORM\GeneratedValue, ORM\Column(type: Types::INTEGER)] + protected int $id; +} \ No newline at end of file diff --git a/src/Entity/UserSystem/User.php b/src/Entity/UserSystem/User.php index b39bea4f8..78f893471 100644 --- a/src/Entity/UserSystem/User.php +++ b/src/Entity/UserSystem/User.php @@ -197,7 +197,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe /** * @var string|null The language/locale the user prefers */ - #[Assert\Language] + #[Assert\Locale] #[Groups(['full', 'import', 'user:read'])] #[ORM\Column(name: 'config_language', type: Types::STRING, nullable: true)] protected ?string $language = ''; diff --git a/src/Entity/UserSystem/WebauthnKey.php b/src/Entity/UserSystem/WebauthnKey.php index b2716e07d..7d3cb7b35 100644 --- a/src/Entity/UserSystem/WebauthnKey.php +++ b/src/Entity/UserSystem/WebauthnKey.php @@ -100,16 +100,19 @@ public function updateLastTimeUsed(): void public static function fromRegistration(BasePublicKeyCredentialSource $registration): self { return new self( - $registration->getPublicKeyCredentialId(), - $registration->getType(), - $registration->getTransports(), - $registration->getAttestationType(), - $registration->getTrustPath(), - $registration->getAaguid(), - $registration->getCredentialPublicKey(), - $registration->getUserHandle(), - $registration->getCounter(), - $registration->getOtherUI() + publicKeyCredentialId: $registration->publicKeyCredentialId, + type: $registration->type, + transports: $registration->transports, + attestationType: $registration->attestationType, + trustPath: $registration->trustPath, + aaguid: $registration->aaguid, + credentialPublicKey: $registration->credentialPublicKey, + userHandle: $registration->userHandle, + counter: $registration->counter, + otherUI: $registration->otherUI, + backupEligible: $registration->backupEligible, + backupStatus: $registration->backupStatus, + uvInitialized: $registration->uvInitialized, ); } } diff --git a/src/EntityListeners/AttachmentDeleteListener.php b/src/EntityListeners/AttachmentDeleteListener.php index e9df5972b..1f39b2d0f 100644 --- a/src/EntityListeners/AttachmentDeleteListener.php +++ b/src/EntityListeners/AttachmentDeleteListener.php @@ -52,8 +52,8 @@ public function __construct(protected AttachmentReverseSearch $attachmentReverse #[PreUpdate] public function preUpdateHandler(Attachment $attachment, PreUpdateEventArgs $event): void { - if ($event->hasChangedField('path')) { - $old_path = $event->getOldValue('path'); + if ($event->hasChangedField('internal_path')) { + $old_path = $event->getOldValue('internal_path'); //Dont delete file if the attachment uses a builtin ressource: if (Attachment::checkIfBuiltin($old_path)) { diff --git a/src/EntityListeners/PartProjectBOMEntryUnlinkListener.php b/src/EntityListeners/PartProjectBOMEntryUnlinkListener.php new file mode 100644 index 000000000..08a93f764 --- /dev/null +++ b/src/EntityListeners/PartProjectBOMEntryUnlinkListener.php @@ -0,0 +1,59 @@ +. + */ + +declare(strict_types=1); + + +namespace App\EntityListeners; + +use App\Entity\Parts\Part; +use Doctrine\Bundle\DoctrineBundle\Attribute\AsEntityListener; +use Doctrine\ORM\Event\PreRemoveEventArgs; + +/** + * If an part is deleted, this listener makes sure that all ProjectBOMEntries that reference this part, are updated + * to not reference the part anymore, but instead store the part name in the name field. + */ +#[AsEntityListener(event: "preRemove", entity: Part::class)] +class PartProjectBOMEntryUnlinkListener +{ + public function preRemove(Part $part, PreRemoveEventArgs $event): void + { + // Iterate over all ProjectBOMEntries that use this part and put the part name into the name field + foreach ($part->getProjectBomEntries() as $bom_entry) { + $old_name = $bom_entry->getName(); + if ($old_name === null || trim($old_name) === '') { + $bom_entry->setName($part->getName()); + } else { + $bom_entry->setName($old_name . ' (' . $part->getName() . ')'); + } + + $old_comment = $bom_entry->getComment(); + if ($old_comment === null || trim($old_comment) === '') { + $bom_entry->setComment('Part was deleted: ' . $part->getName()); + } else { + $bom_entry->setComment($old_comment . "\n\n Part was deleted: " . $part->getName()); + } + + //Remove the part reference + $bom_entry->setPart(null); + } + } +} diff --git a/src/EventListener/LogSystem/EventLoggerListener.php b/src/EventListener/LogSystem/EventLoggerListener.php index 6fe3d8dc0..f5029c280 100644 --- a/src/EventListener/LogSystem/EventLoggerListener.php +++ b/src/EventListener/LogSystem/EventLoggerListener.php @@ -39,6 +39,8 @@ use App\Services\LogSystem\EventLogger; use App\Services\LogSystem\EventUndoHelper; use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener; +use App\Settings\SystemSettings\HistorySettings; +use Doctrine\Common\EventSubscriber; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Event\OnFlushEventArgs; use Doctrine\ORM\Event\PostFlushEventArgs; @@ -74,14 +76,15 @@ class EventLoggerListener ]; protected const MAX_STRING_LENGTH = 2000; - protected bool $save_new_data; - public function __construct(protected EventLogger $logger, protected SerializerInterface $serializer, protected EventCommentHelper $eventCommentHelper, - protected bool $save_changed_fields, protected bool $save_changed_data, protected bool $save_removed_data, bool $save_new_data, - protected PropertyAccessorInterface $propertyAccessor, protected EventUndoHelper $eventUndoHelper) + public function __construct( + protected EventLogger $logger, + protected SerializerInterface $serializer, + protected EventCommentHelper $eventCommentHelper, + private readonly HistorySettings $settings, + protected PropertyAccessorInterface $propertyAccessor, + protected EventUndoHelper $eventUndoHelper) { - //This option only makes sense if save_changed_data is true - $this->save_new_data = $save_new_data && $save_changed_data; } public function onFlush(OnFlushEventArgs $eventArgs): void @@ -167,6 +170,7 @@ public function postFlush(PostFlushEventArgs $args): void public function hasFieldRestrictions(AbstractDBElement $element): bool { foreach (array_keys(static::FIELD_BLACKLIST) as $class) { + /** @var string $class */ if ($element instanceof $class) { return true; } @@ -181,6 +185,7 @@ public function hasFieldRestrictions(AbstractDBElement $element): bool public function shouldFieldBeSaved(AbstractDBElement $element, string $field_name): bool { foreach (static::FIELD_BLACKLIST as $class => $blacklist) { + /** @var string $class */ if ($element instanceof $class && in_array($field_name, $blacklist, true)) { return false; } @@ -200,18 +205,19 @@ protected function logElementDeleted(AbstractDBElement $entity, EntityManagerInt if ($this->eventUndoHelper->isUndo()) { $log->setUndoneEvent($this->eventUndoHelper->getUndoneEvent(), $this->eventUndoHelper->getMode()); } - if ($this->save_removed_data) { + if ($this->settings->saveRemovedData) { //The 4th param is important here, as we delete the element... $this->saveChangeSet($entity, $log, $em, true); } $this->logger->logFromOnFlush($log); //Check if we have to log CollectionElementDeleted entries - if ($this->save_changed_data) { + if ($this->settings->saveOldData) { $metadata = $em->getClassMetadata($entity::class); $mappings = $metadata->getAssociationMappings(); //Check if class is whitelisted for CollectionElementDeleted entry foreach (static::TRIGGER_ASSOCIATION_LOG_WHITELIST as $class => $whitelist) { + /** @var string $class */ if ($entity instanceof $class) { //Check names foreach ($mappings as $field => $mapping) { @@ -243,9 +249,9 @@ protected function logElementEdited(AbstractDBElement $entity, EntityManagerInte } $log = new ElementEditedLogEntry($entity); - if ($this->save_changed_data) { + if ($this->settings->saveOldData) { $this->saveChangeSet($entity, $log, $em); - } elseif ($this->save_changed_fields) { + } elseif ($this->settings->saveChangedFields) { $changed_fields = array_keys($uow->getEntityChangeSet($entity)); //Remove lastModified field, as this is always changed (gives us no additional info) $changed_fields = array_diff($changed_fields, ['lastModified']); @@ -313,7 +319,7 @@ protected function saveChangeSet(AbstractDBElement $entity, AbstractLogEntry $lo $changeSet = $uow->getEntityChangeSet($entity); $old_data = array_combine(array_keys($changeSet), array_column($changeSet, 0)); //If save_new_data is enabled, we extract it from the change set - if ($this->save_new_data) { + if ($this->settings->saveNewData && $this->settings->saveOldData) { //Only useful if we save old data too $new_data = array_combine(array_keys($changeSet), array_column($changeSet, 1)); } } diff --git a/src/EventSubscriber/SymfonyDebugToolbarSubscriber.php b/src/EventSubscriber/SymfonyDebugToolbarSubscriber.php deleted file mode 100644 index 6f17e3992..000000000 --- a/src/EventSubscriber/SymfonyDebugToolbarSubscriber.php +++ /dev/null @@ -1,69 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace App\EventSubscriber; - -use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpKernel\Event\ResponseEvent; - -/** - * This subscriber sets a Header in Debug mode that signals the Symfony Profiler to also update on Ajax requests. - */ -final class SymfonyDebugToolbarSubscriber implements EventSubscriberInterface -{ - public function __construct(private readonly bool $kernel_debug_enabled) - { - } - - /** - * Returns an array of event names this subscriber wants to listen to. - * - * The array keys are event names and the value can be: - * - * * The method name to call (priority defaults to 0) - * * An array composed of the method name to call and the priority - * * An array of arrays composed of the method names to call and respective - * priorities, or 0 if unset - * - * For instance: - * - * * ['eventName' => 'methodName'] - * * ['eventName' => ['methodName', $priority]] - * * ['eventName' => [['methodName1', $priority], ['methodName2']]] - * - * @return array The event names to listen to - */ - public static function getSubscribedEvents(): array - { - return ['kernel.response' => 'onKernelResponse']; - } - - public function onKernelResponse(ResponseEvent $event): void - { - if (!$this->kernel_debug_enabled) { - return; - } - - $response = $event->getResponse(); - $response->headers->set('Symfony-Debug-Toolbar-Replace', '1'); - } -} diff --git a/src/EventSubscriber/UserSystem/SetUserTimezoneSubscriber.php b/src/EventSubscriber/UserSystem/SetUserTimezoneSubscriber.php index 10ecaddf3..9964c6188 100644 --- a/src/EventSubscriber/UserSystem/SetUserTimezoneSubscriber.php +++ b/src/EventSubscriber/UserSystem/SetUserTimezoneSubscriber.php @@ -23,6 +23,7 @@ namespace App\EventSubscriber\UserSystem; use App\Entity\UserSystem\User; +use App\Settings\SystemSettings\LocalizationSettings; use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\ControllerEvent; @@ -33,7 +34,7 @@ */ final class SetUserTimezoneSubscriber implements EventSubscriberInterface { - public function __construct(private readonly string $default_timezone, private readonly Security $security) + public function __construct(private readonly LocalizationSettings $localizationSettings, private readonly Security $security) { } @@ -48,8 +49,8 @@ public function setTimeZone(ControllerEvent $event): void } //Fill with default value if needed - if (null === $timezone && $this->default_timezone !== '') { - $timezone = $this->default_timezone; + if (null === $timezone && $this->localizationSettings->timezone !== '') { + $timezone = $this->localizationSettings->timezone; } //If timezone was configured anywhere set it, otherwise just use the one from php.ini diff --git a/src/Exceptions/OAuthReconnectRequiredException.php b/src/Exceptions/OAuthReconnectRequiredException.php new file mode 100644 index 000000000..97abb19f6 --- /dev/null +++ b/src/Exceptions/OAuthReconnectRequiredException.php @@ -0,0 +1,48 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Exceptions; + +use Throwable; + +class OAuthReconnectRequiredException extends \RuntimeException +{ + private string $providerName = "unknown"; + + public function __construct(string $message = "You need to reconnect the OAuth connection for this provider!", int $code = 0, ?Throwable $previous = null) + { + parent::__construct($message, $code, $previous); + } + + public static function forProvider(string $providerName): self + { + $exception = new self("You need to reconnect the OAuth connection for the provider '$providerName'!"); + $exception->providerName = $providerName; + return $exception; + } + + public function getProviderName(): string + { + return $this->providerName; + } +} diff --git a/src/Form/AdminPages/BaseEntityAdminForm.php b/src/Form/AdminPages/BaseEntityAdminForm.php index d1a0ffd0f..5a4ef5bce 100644 --- a/src/Form/AdminPages/BaseEntityAdminForm.php +++ b/src/Form/AdminPages/BaseEntityAdminForm.php @@ -25,6 +25,7 @@ use App\Entity\PriceInformations\Currency; use App\Entity\ProjectSystem\Project; use App\Entity\UserSystem\Group; +use App\Services\LogSystem\EventCommentType; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Base\AbstractNamedDBElement; use App\Entity\Base\AbstractStructuralDBElement; @@ -152,7 +153,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void $builder->add('log_comment', TextType::class, [ 'label' => 'edit.log_comment', 'mapped' => false, - 'required' => $this->eventCommentNeededHelper->isCommentNeeded($is_new ? 'datastructure_create': 'datastructure_edit'), + 'required' => $this->eventCommentNeededHelper->isCommentNeeded($is_new ? EventCommentType::DATASTRUCTURE_CREATE: EventCommentType::DATASTRUCTURE_EDIT), 'empty_data' => null, ]); diff --git a/src/Form/AdminPages/CurrencyAdminForm.php b/src/Form/AdminPages/CurrencyAdminForm.php index 0fab055dc..afcf3c1ff 100644 --- a/src/Form/AdminPages/CurrencyAdminForm.php +++ b/src/Form/AdminPages/CurrencyAdminForm.php @@ -22,6 +22,7 @@ namespace App\Form\AdminPages; +use App\Settings\SystemSettings\LocalizationSettings; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Base\AbstractNamedDBElement; use App\Form\Type\BigDecimalMoneyType; @@ -32,7 +33,7 @@ class CurrencyAdminForm extends BaseEntityAdminForm { - public function __construct(Security $security, EventCommentNeededHelper $eventCommentNeededHelper, private readonly string $base_currency) + public function __construct(Security $security, EventCommentNeededHelper $eventCommentNeededHelper, private readonly LocalizationSettings $localizationSettings) { parent::__construct($security, $eventCommentNeededHelper); } @@ -51,7 +52,7 @@ protected function additionalFormElements(FormBuilderInterface $builder, array $ $builder->add('exchange_rate', BigDecimalMoneyType::class, [ 'required' => false, 'label' => 'currency.edit.exchange_rate', - 'currency' => $this->base_currency, + 'currency' => $this->localizationSettings->baseCurrency, 'scale' => 6, 'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity), ]); diff --git a/src/Form/AdminPages/ImportType.php b/src/Form/AdminPages/ImportType.php index 3e87812c7..0bd3cea1e 100644 --- a/src/Form/AdminPages/ImportType.php +++ b/src/Form/AdminPages/ImportType.php @@ -59,6 +59,8 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'XML' => 'xml', 'CSV' => 'csv', 'YAML' => 'yaml', + 'XLSX' => 'xlsx', + 'XLS' => 'xls', ], 'label' => 'export.format', 'disabled' => $disabled, diff --git a/src/Form/AdminPages/PartCustomStateAdminForm.php b/src/Form/AdminPages/PartCustomStateAdminForm.php new file mode 100644 index 000000000..b8bb2815e --- /dev/null +++ b/src/Form/AdminPages/PartCustomStateAdminForm.php @@ -0,0 +1,27 @@ +. + */ + +declare(strict_types=1); + +namespace App\Form\AdminPages; + +class PartCustomStateAdminForm extends BaseEntityAdminForm +{ +} diff --git a/src/Form/AdminPages/SupplierForm.php b/src/Form/AdminPages/SupplierForm.php index 34b3b27aa..43ac06165 100644 --- a/src/Form/AdminPages/SupplierForm.php +++ b/src/Form/AdminPages/SupplierForm.php @@ -22,6 +22,7 @@ namespace App\Form\AdminPages; +use App\Settings\SystemSettings\LocalizationSettings; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Base\AbstractNamedDBElement; use App\Entity\PriceInformations\Currency; @@ -32,7 +33,7 @@ class SupplierForm extends CompanyForm { - public function __construct(Security $security, EventCommentNeededHelper $eventCommentNeededHelper, protected string $base_currency) + public function __construct(Security $security, EventCommentNeededHelper $eventCommentNeededHelper, private readonly LocalizationSettings $localizationSettings) { parent::__construct($security, $eventCommentNeededHelper); } @@ -53,7 +54,7 @@ protected function additionalFormElements(FormBuilderInterface $builder, array $ $builder->add('shipping_costs', BigDecimalMoneyType::class, [ 'required' => false, - 'currency' => $this->base_currency, + 'currency' => $this->localizationSettings->baseCurrency, 'scale' => 3, 'label' => 'supplier.shipping_costs.label', 'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity), diff --git a/src/Form/AttachmentFormType.php b/src/Form/AttachmentFormType.php index 957d692b1..eb484a588 100644 --- a/src/Form/AttachmentFormType.php +++ b/src/Form/AttachmentFormType.php @@ -22,6 +22,7 @@ namespace App\Form; +use App\Settings\SystemSettings\AttachmentsSettings; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentType; @@ -54,9 +55,7 @@ public function __construct( protected Security $security, protected AttachmentSubmitHandler $submitHandler, protected TranslatorInterface $translator, - protected bool $allow_attachments_download, - protected bool $download_by_default, - protected string $max_file_size + protected AttachmentsSettings $settings, ) { } @@ -108,7 +107,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'required' => false, 'label' => 'attachment.edit.download_url', 'mapped' => false, - 'disabled' => !$this->allow_attachments_download, + 'disabled' => !$this->settings->allowDownloads, ]); $builder->add('file', FileType::class, [ @@ -177,7 +176,7 @@ static function (FormEvent $event): void { //If the attachment should be downloaded by default (and is download allowed at all), register a listener, // which sets the downloadURL checkbox to true for new attachments - if ($this->download_by_default && $this->allow_attachments_download) { + if ($this->settings->downloadByDefault && $this->settings->allowDownloads) { $builder->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event): void { $form = $event->getForm(); $attachment = $form->getData(); @@ -204,7 +203,7 @@ public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'data_class' => Attachment::class, - 'max_file_size' => $this->max_file_size, + 'max_file_size' => $this->settings->maxFileSize, 'allow_builtins' => true, ]); } diff --git a/src/Form/CollectionTypeExtension.php b/src/Form/Extension/CollectionTypeExtension.php similarity index 99% rename from src/Form/CollectionTypeExtension.php rename to src/Form/Extension/CollectionTypeExtension.php index 4fa938524..52cd41866 100644 --- a/src/Form/CollectionTypeExtension.php +++ b/src/Form/Extension/CollectionTypeExtension.php @@ -39,7 +39,7 @@ * along with this program. If not, see . */ -namespace App\Form; +namespace App\Form\Extension; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; diff --git a/src/Form/PasswordTypeExtension.php b/src/Form/Extension/PasswordTypeExtension.php similarity index 67% rename from src/Form/PasswordTypeExtension.php rename to src/Form/Extension/PasswordTypeExtension.php index 64711c53d..cc0486b03 100644 --- a/src/Form/PasswordTypeExtension.php +++ b/src/Form/Extension/PasswordTypeExtension.php @@ -1,4 +1,22 @@ . + */ declare(strict_types=1); @@ -20,7 +38,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -namespace App\Form; +namespace App\Form\Extension; use Symfony\Component\Form\AbstractTypeExtension; use Symfony\Component\Form\Extension\Core\Type\PasswordType; diff --git a/src/Form/Extension/SelectTypeOrderExtension.php b/src/Form/Extension/SelectTypeOrderExtension.php new file mode 100644 index 000000000..e8e9a93f9 --- /dev/null +++ b/src/Form/Extension/SelectTypeOrderExtension.php @@ -0,0 +1,60 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Form\Extension; + +use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\EnumType; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class SelectTypeOrderExtension extends AbstractTypeExtension +{ + public static function getExtendedTypes(): iterable + { + return [ + ChoiceType::class, + EnumType::class + ]; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefault('ordered', false); + $resolver->setDefault('by_reference', function (Options $options) { + //Disable by_reference if the field is ordered (otherwise the order will be lost) + return !$options['ordered']; + }); + } + + public function buildView(FormView $view, FormInterface $form, array $options): void + { + //Pass the data in ordered form to the frontend controller, so it can make the items appear in the correct order. + if ($options['ordered']) { + $view->vars['attr']['data-ordered-value'] = json_encode($form->getViewData(), JSON_THROW_ON_ERROR); + } + } +} diff --git a/src/Form/Extension/TogglePasswordTypeExtension.php b/src/Form/Extension/TogglePasswordTypeExtension.php new file mode 100644 index 000000000..8f7320df9 --- /dev/null +++ b/src/Form/Extension/TogglePasswordTypeExtension.php @@ -0,0 +1,122 @@ +. + */ + +declare(strict_types=1); + +namespace App\Form\Extension; + +use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\Form\Extension\Core\Type\PasswordType; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Translation\TranslatableMessage; +use Symfony\Contracts\Translation\TranslatorInterface; + +final class TogglePasswordTypeExtension extends AbstractTypeExtension +{ + public function __construct(private readonly ?TranslatorInterface $translator) + { + } + + public static function getExtendedTypes(): iterable + { + return [PasswordType::class]; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'toggle' => false, + 'hidden_label' => 'Hide', + 'visible_label' => 'Show', + 'hidden_icon' => 'Default', + 'visible_icon' => 'Default', + 'button_classes' => ['toggle-password-button'], + 'toggle_container_classes' => ['toggle-password-container'], + 'toggle_translation_domain' => null, + 'use_toggle_form_theme' => true, + ]); + + $resolver->setNormalizer( + 'toggle_translation_domain', + static fn (Options $options, $labelTranslationDomain) => $labelTranslationDomain ?? $options['translation_domain'], + ); + + $resolver->setAllowedTypes('toggle', ['bool']); + $resolver->setAllowedTypes('hidden_label', ['string', TranslatableMessage::class, 'null']); + $resolver->setAllowedTypes('visible_label', ['string', TranslatableMessage::class, 'null']); + $resolver->setAllowedTypes('hidden_icon', ['string', 'null']); + $resolver->setAllowedTypes('visible_icon', ['string', 'null']); + $resolver->setAllowedTypes('button_classes', ['string[]']); + $resolver->setAllowedTypes('toggle_container_classes', ['string[]']); + $resolver->setAllowedTypes('toggle_translation_domain', ['string', 'bool', 'null']); + $resolver->setAllowedTypes('use_toggle_form_theme', ['bool']); + } + + public function buildView(FormView $view, FormInterface $form, array $options): void + { + $view->vars['toggle'] = $options['toggle']; + + if (!$options['toggle']) { + return; + } + + if ($options['use_toggle_form_theme']) { + array_splice($view->vars['block_prefixes'], -1, 0, 'toggle_password'); + } + + $controllerName = 'toggle-password'; + $controllerValues = []; + $view->vars['attr']['data-controller'] = trim(\sprintf('%s %s', $view->vars['attr']['data-controller'] ?? '', $controllerName)); + + if (false !== $options['toggle_translation_domain']) { + $controllerValues['hidden-label'] = $this->translateLabel($options['hidden_label'], $options['toggle_translation_domain']); + $controllerValues['visible-label'] = $this->translateLabel($options['visible_label'], $options['toggle_translation_domain']); + } else { + $controllerValues['hidden-label'] = $options['hidden_label']; + $controllerValues['visible-label'] = $options['visible_label']; + } + + $controllerValues['hidden-icon'] = $options['hidden_icon']; + $controllerValues['visible-icon'] = $options['visible_icon']; + $controllerValues['button-classes'] = json_encode($options['button_classes'], \JSON_THROW_ON_ERROR); + + foreach ($controllerValues as $name => $value) { + $view->vars['attr'][\sprintf('data-%s-%s-value', $controllerName, $name)] = $value; + } + + $view->vars['toggle_container_classes'] = $options['toggle_container_classes']; + } + + private function translateLabel(string|TranslatableMessage|null $label, ?string $translationDomain): ?string + { + if (null === $this->translator || null === $label) { + return $label; + } + + if ($label instanceof TranslatableMessage) { + return $label->trans($this->translator); + } + + return $this->translator->trans($label, domain: $translationDomain); + } +} diff --git a/src/Form/Filters/AttachmentFilterType.php b/src/Form/Filters/AttachmentFilterType.php index e6746feb8..ff80bd384 100644 --- a/src/Form/Filters/AttachmentFilterType.php +++ b/src/Form/Filters/AttachmentFilterType.php @@ -100,6 +100,15 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'label' => 'attachment.edit.show_in_table' ]); + $builder->add('originalFileName', TextConstraintType::class, [ + 'label' => 'attachment.file_name' + ]); + + $builder->add('externalLink', TextConstraintType::class, [ + 'label' => 'attachment.table.external_link' + ]); + + $builder->add('lastModified', DateTimeConstraintType::class, [ 'label' => 'lastModified' ]); diff --git a/src/Form/Filters/LogFilterType.php b/src/Form/Filters/LogFilterType.php index 42b367b74..30abf723d 100644 --- a/src/Form/Filters/LogFilterType.php +++ b/src/Form/Filters/LogFilterType.php @@ -100,7 +100,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void ]); $builder->add('user', UserEntityConstraintType::class, [ - 'label' => 'log.user', + 'label' => 'log.user', ]); $builder->add('targetType', EnumConstraintType::class, [ @@ -128,11 +128,14 @@ public function buildForm(FormBuilderInterface $builder, array $options): void LogTargetType::PARAMETER => 'parameter.label', LogTargetType::LABEL_PROFILE => 'label_profile.label', LogTargetType::PART_ASSOCIATION => 'part_association.label', + LogTargetType::BULK_INFO_PROVIDER_IMPORT_JOB => 'bulk_info_provider_import_job.label', + LogTargetType::BULK_INFO_PROVIDER_IMPORT_JOB_PART => 'bulk_info_provider_import_job_part.label', + LogTargetType::PART_CUSTOM_STATE => 'part_custom_state.label', }, ]); $builder->add('targetId', NumberConstraintType::class, [ - 'label' => 'log.target_id', + 'label' => 'log.target_id', 'min' => 1, 'step' => 1, ]); diff --git a/src/Form/Filters/PartFilterType.php b/src/Form/Filters/PartFilterType.php index dfe449d18..e101c6351 100644 --- a/src/Form/Filters/PartFilterType.php +++ b/src/Form/Filters/PartFilterType.php @@ -22,19 +22,27 @@ */ namespace App\Form\Filters; +use App\DataTables\Filters\Constraints\Part\BulkImportPartStatusConstraint; use App\DataTables\Filters\Constraints\Part\ParameterConstraint; use App\DataTables\Filters\PartFilter; use App\Entity\Attachments\AttachmentType; +use App\Entity\InfoProviderSystem\BulkImportJobStatus; +use App\Entity\InfoProviderSystem\BulkImportPartStatus; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; +use App\Entity\Parts\PartCustomState; use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\ProjectSystem\Project; use App\Form\Filters\Constraints\BooleanConstraintType; +use App\Form\Filters\Constraints\BulkImportJobExistsConstraintType; +use App\Form\Filters\Constraints\BulkImportJobStatusConstraintType; +use App\Form\Filters\Constraints\BulkImportPartStatusConstraintType; use App\Form\Filters\Constraints\ChoiceConstraintType; use App\Form\Filters\Constraints\DateTimeConstraintType; +use App\Form\Filters\Constraints\EnumConstraintType; use App\Form\Filters\Constraints\NumberConstraintType; use App\Form\Filters\Constraints\ParameterConstraintType; use App\Form\Filters\Constraints\StructuralEntityConstraintType; @@ -50,6 +58,8 @@ use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; +use function Symfony\Component\Translation\t; + class PartFilterType extends AbstractType { public function __construct(private readonly Security $security) @@ -130,6 +140,11 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'entity_class' => MeasurementUnit::class ]); + $builder->add('partCustomState', StructuralEntityConstraintType::class, [ + 'label' => 'part.edit.partCustomState', + 'entity_class' => PartCustomState::class + ]); + $builder->add('lastModified', DateTimeConstraintType::class, [ 'label' => 'lastModified' ]); @@ -298,6 +313,31 @@ public function buildForm(FormBuilderInterface $builder, array $options): void } + /************************************************************************** + * Bulk Import Job tab + **************************************************************************/ + if ($this->security->isGranted('@info_providers.create_parts')) { + $builder + ->add('inBulkImportJob', BooleanConstraintType::class, [ + 'label' => 'part.filter.in_bulk_import_job', + ]) + ->add('bulkImportJobStatus', EnumConstraintType::class, [ + 'enum_class' => BulkImportJobStatus::class, + 'label' => 'part.filter.bulk_import_job_status', + 'choice_label' => function (BulkImportJobStatus $value) { + return t('bulk_import.status.' . $value->value); + }, + ]) + ->add('bulkImportPartStatus', EnumConstraintType::class, [ + 'enum_class' => BulkImportPartStatus::class, + 'label' => 'part.filter.bulk_import_part_status', + 'choice_label' => function (BulkImportPartStatus $value) { + return t('bulk_import.part_status.' . $value->value); + }, + ]) + ; + } + $builder->add('submit', SubmitType::class, [ 'label' => 'filter.submit', diff --git a/src/Form/History/EnforceEventCommentTypesType.php b/src/Form/History/EnforceEventCommentTypesType.php new file mode 100644 index 000000000..85e43e6e7 --- /dev/null +++ b/src/Form/History/EnforceEventCommentTypesType.php @@ -0,0 +1,49 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Form\History; + +use App\Services\LogSystem\EventCommentType; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\EnumType; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * The type for the "enforceComments" setting in the HistorySettings. + */ +class EnforceEventCommentTypesType extends AbstractType +{ + public function getParent(): string + { + return EnumType::class; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'multiple' => true, + 'class' => EventCommentType::class, + 'empty_data' => [], + ]); + } +} diff --git a/src/Form/InfoProviderSystem/BulkProviderSearchType.php b/src/Form/InfoProviderSystem/BulkProviderSearchType.php new file mode 100644 index 000000000..24a3cfb4b --- /dev/null +++ b/src/Form/InfoProviderSystem/BulkProviderSearchType.php @@ -0,0 +1,62 @@ +. + */ + +declare(strict_types=1); + +namespace App\Form\InfoProviderSystem; + +use App\Entity\Parts\Part; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\CollectionType; +use Symfony\Component\Form\Extension\Core\Type\HiddenType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class BulkProviderSearchType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $parts = $options['parts']; + + $builder->add('part_configurations', CollectionType::class, [ + 'entry_type' => PartProviderConfigurationType::class, + 'entry_options' => [ + 'label' => false, + ], + 'allow_add' => false, + 'allow_delete' => false, + 'label' => false, + ]); + + $builder->add('submit', SubmitType::class, [ + 'label' => 'info_providers.bulk_search.submit' + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'parts' => [], + ]); + $resolver->setRequired('parts'); + } +} \ No newline at end of file diff --git a/src/Form/InfoProviderSystem/FieldToProviderMappingType.php b/src/Form/InfoProviderSystem/FieldToProviderMappingType.php new file mode 100644 index 000000000..13e9581ee --- /dev/null +++ b/src/Form/InfoProviderSystem/FieldToProviderMappingType.php @@ -0,0 +1,75 @@ +. + */ + +declare(strict_types=1); + +namespace App\Form\InfoProviderSystem; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\IntegerType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class FieldToProviderMappingType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $fieldChoices = $options['field_choices'] ?? []; + + $builder->add('field', ChoiceType::class, [ + 'label' => 'info_providers.bulk_search.search_field', + 'choices' => $fieldChoices, + 'expanded' => false, + 'multiple' => false, + 'required' => false, + 'placeholder' => 'info_providers.bulk_search.field.select', + ]); + + $builder->add('providers', ProviderSelectType::class, [ + 'label' => 'info_providers.bulk_search.providers', + 'help' => 'info_providers.bulk_search.providers.help', + 'required' => false, + ]); + + $builder->add('priority', IntegerType::class, [ + 'label' => 'info_providers.bulk_search.priority', + 'help' => 'info_providers.bulk_search.priority.help', + 'required' => false, + 'data' => 1, // Default priority + 'attr' => [ + 'min' => 1, + 'max' => 10, + 'class' => 'form-control-sm', + 'style' => 'width: 80px;' + ], + 'constraints' => [ + new \Symfony\Component\Validator\Constraints\Range(['min' => 1, 'max' => 10]), + ], + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'field_choices' => [], + ]); + } +} diff --git a/src/Form/InfoProviderSystem/GlobalFieldMappingType.php b/src/Form/InfoProviderSystem/GlobalFieldMappingType.php new file mode 100644 index 000000000..ea70284f5 --- /dev/null +++ b/src/Form/InfoProviderSystem/GlobalFieldMappingType.php @@ -0,0 +1,67 @@ +. + */ + +declare(strict_types=1); + +namespace App\Form\InfoProviderSystem; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; +use Symfony\Component\Form\Extension\Core\Type\CollectionType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class GlobalFieldMappingType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $fieldChoices = $options['field_choices'] ?? []; + + $builder->add('field_mappings', CollectionType::class, [ + 'entry_type' => FieldToProviderMappingType::class, + 'entry_options' => [ + 'label' => false, + 'field_choices' => $fieldChoices, + ], + 'allow_add' => true, + 'allow_delete' => true, + 'prototype' => true, + 'label' => false, + ]); + + $builder->add('prefetch_details', CheckboxType::class, [ + 'label' => 'info_providers.bulk_import.prefetch_details', + 'required' => false, + 'help' => 'info_providers.bulk_import.prefetch_details_help', + ]); + + $builder->add('submit', SubmitType::class, [ + 'label' => 'info_providers.bulk_import.search.submit' + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'field_choices' => [], + ]); + } +} \ No newline at end of file diff --git a/src/Form/InfoProviderSystem/PartProviderConfigurationType.php b/src/Form/InfoProviderSystem/PartProviderConfigurationType.php new file mode 100644 index 000000000..cecf62a33 --- /dev/null +++ b/src/Form/InfoProviderSystem/PartProviderConfigurationType.php @@ -0,0 +1,55 @@ +. + */ + +declare(strict_types=1); + +namespace App\Form\InfoProviderSystem; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\HiddenType; +use Symfony\Component\Form\FormBuilderInterface; + +class PartProviderConfigurationType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder->add('part_id', HiddenType::class); + + $builder->add('search_field', ChoiceType::class, [ + 'label' => 'info_providers.bulk_search.search_field', + 'choices' => [ + 'info_providers.bulk_search.field.mpn' => 'mpn', + 'info_providers.bulk_search.field.name' => 'name', + 'info_providers.bulk_search.field.digikey_spn' => 'digikey_spn', + 'info_providers.bulk_search.field.mouser_spn' => 'mouser_spn', + 'info_providers.bulk_search.field.lcsc_spn' => 'lcsc_spn', + 'info_providers.bulk_search.field.farnell_spn' => 'farnell_spn', + ], + 'expanded' => false, + 'multiple' => false, + ]); + + $builder->add('providers', ProviderSelectType::class, [ + 'label' => 'info_providers.bulk_search.providers', + 'help' => 'info_providers.bulk_search.providers.help', + ]); + } +} \ No newline at end of file diff --git a/src/Form/InfoProviderSystem/ProviderSelectType.php b/src/Form/InfoProviderSystem/ProviderSelectType.php index a93733900..95e107911 100644 --- a/src/Form/InfoProviderSystem/ProviderSelectType.php +++ b/src/Form/InfoProviderSystem/ProviderSelectType.php @@ -28,6 +28,7 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\ChoiceList\ChoiceList; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; class ProviderSelectType extends AbstractType @@ -44,13 +45,43 @@ public function getParent(): string public function configureOptions(OptionsResolver $resolver): void { - $resolver->setDefaults([ - 'choices' => $this->providerRegistry->getActiveProviders(), - 'choice_label' => ChoiceList::label($this, static fn (?InfoProviderInterface $choice) => $choice?->getProviderInfo()['name']), - 'choice_value' => ChoiceList::value($this, static fn(?InfoProviderInterface $choice) => $choice?->getProviderKey()), + $providers = $this->providerRegistry->getActiveProviders(); - 'multiple' => true, - ]); + $resolver->setDefault('input', 'object'); + $resolver->setAllowedTypes('input', 'string'); + //Either the form returns the provider objects or their keys + $resolver->setAllowedValues('input', ['object', 'string']); + $resolver->setDefault('multiple', true); + + $resolver->setDefault('choices', function (Options $options) use ($providers) { + if ('object' === $options['input']) { + return $this->providerRegistry->getActiveProviders(); + } + + $tmp = []; + foreach ($providers as $provider) { + $name = $provider->getProviderInfo()['name']; + $tmp[$name] = $provider->getProviderKey(); + } + + return $tmp; + }); + + //The choice_label and choice_value only needs to be set if we want the objects + $resolver->setDefault('choice_label', function (Options $options){ + if ('object' === $options['input']) { + return ChoiceList::label($this, static fn (?InfoProviderInterface $choice) => $choice?->getProviderInfo()['name']); + } + + return null; + }); + $resolver->setDefault('choice_value', function (Options $options) { + if ('object' === $options['input']) { + return ChoiceList::value($this, static fn(?InfoProviderInterface $choice) => $choice?->getProviderKey()); + } + + return null; + }); } -} \ No newline at end of file +} diff --git a/src/Form/LabelSystem/LabelDialogType.php b/src/Form/LabelSystem/LabelDialogType.php index f2710b19f..d79d01f6e 100644 --- a/src/Form/LabelSystem/LabelDialogType.php +++ b/src/Form/LabelSystem/LabelDialogType.php @@ -87,6 +87,16 @@ public function buildForm(FormBuilderInterface $builder, array $options): void ] ]); + if ($options['profile'] !== null) { + $builder->add('update_profile', SubmitType::class, [ + 'label' => 'label_generator.update_profile', + 'disabled' => !$this->security->isGranted('edit', $options['profile']), + 'attr' => [ + 'class' => 'btn btn-outline-success' + ] + ]); + } + $builder->add('update', SubmitType::class, [ 'label' => 'label_generator.update', ]); @@ -97,5 +107,6 @@ public function configureOptions(OptionsResolver $resolver): void parent::configureOptions($resolver); $resolver->setDefault('mapped', false); $resolver->setDefault('disable_options', false); + $resolver->setDefault('profile', null); } } diff --git a/src/Form/Part/PartBaseType.php b/src/Form/Part/PartBaseType.php index b1d2ebeaa..f0ba180e0 100644 --- a/src/Form/Part/PartBaseType.php +++ b/src/Form/Part/PartBaseType.php @@ -30,6 +30,7 @@ use App\Entity\Parts\ManufacturingStatus; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; +use App\Entity\Parts\PartCustomState; use App\Entity\PriceInformations\Orderdetail; use App\Form\AttachmentFormType; use App\Form\ParameterType; @@ -40,6 +41,7 @@ use App\Form\Type\StructuralEntityType; use App\Services\InfoProviderSystem\DTOs\PartDetailDTO; use App\Services\LogSystem\EventCommentNeededHelper; +use App\Services\LogSystem\EventCommentType; use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; @@ -170,6 +172,12 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'disable_not_selectable' => true, 'label' => 'part.edit.partUnit', ]) + ->add('partCustomState', StructuralEntityType::class, [ + 'class' => PartCustomState::class, + 'required' => false, + 'disable_not_selectable' => true, + 'label' => 'part.edit.partCustomState', + ]) ->add('ipn', TextType::class, [ 'required' => false, 'empty_data' => null, @@ -265,7 +273,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void $builder->add('log_comment', TextType::class, [ 'label' => 'edit.log_comment', 'mapped' => false, - 'required' => $this->event_comment_needed_helper->isCommentNeeded($new_part ? 'part_create' : 'part_edit'), + 'required' => $this->event_comment_needed_helper->isCommentNeeded($new_part ? EventCommentType::PART_CREATE : EventCommentType::PART_EDIT), 'empty_data' => null, ]); diff --git a/src/Form/Type/APIKeyType.php b/src/Form/Type/APIKeyType.php new file mode 100644 index 000000000..57eaea964 --- /dev/null +++ b/src/Form/Type/APIKeyType.php @@ -0,0 +1,81 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Form\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\PasswordType; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Contracts\Translation\TranslatorInterface; + +class APIKeyType extends AbstractType +{ + public function __construct(private readonly TranslatorInterface $translator) + { + } + + public function getParent(): string + { + return PasswordType::class; + } + + public function buildView(FormView $view, FormInterface $form, array $options): void + { + $viewData = $form->getViewData(); + + //If the field is disabled, show the redacted API key + if ($options['disabled'] ?? false) { + if ($viewData === null || $viewData === '') { + $view->vars['value'] = $viewData; + } else { + + $view->vars['value'] = self::redact((string)$viewData) . ' (' . $this ->translator->trans("form.apikey.redacted") . ')'; + } + } else { //Otherwise, show the actual value + $view->vars['value'] = $viewData; + } + } + + public static function redact(string $apiKey): string + { + //Show only the last 2 characters of the API key if it is long enough (more than 16 characters) + //Replace all other characters with dots + if (strlen($apiKey) > 16) { + return str_repeat('*', strlen($apiKey) - 2) . substr($apiKey, -2); + } + + return str_repeat('*', strlen($apiKey)); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'always_empty' => false, + 'toggle' => true, + 'empty_data' => null, + 'attr' => ['autocomplete' => 'off'], + ]); + } +} diff --git a/src/Form/Type/CurrencyEntityType.php b/src/Form/Type/CurrencyEntityType.php index 07f0a9f89..875ca35f9 100644 --- a/src/Form/Type/CurrencyEntityType.php +++ b/src/Form/Type/CurrencyEntityType.php @@ -25,6 +25,7 @@ use App\Entity\PriceInformations\Currency; use App\Form\Type\Helper\StructuralEntityChoiceHelper; use App\Services\Trees\NodesListBuilder; +use App\Settings\SystemSettings\LocalizationSettings; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Intl\Currencies; use Symfony\Component\OptionsResolver\Options; @@ -36,7 +37,7 @@ */ class CurrencyEntityType extends StructuralEntityType { - public function __construct(EntityManagerInterface $em, NodesListBuilder $builder, TranslatorInterface $translator, StructuralEntityChoiceHelper $choiceHelper, protected ?string $base_currency) + public function __construct(EntityManagerInterface $em, NodesListBuilder $builder, TranslatorInterface $translator, StructuralEntityChoiceHelper $choiceHelper, private readonly LocalizationSettings $localizationSettings) { parent::__construct($em, $builder, $translator, $choiceHelper); } @@ -57,7 +58,7 @@ public function configureOptions(OptionsResolver $resolver): void $resolver->setDefault('empty_message', function (Options $options) { //By default, we use the global base currency: - $iso_code = $this->base_currency; + $iso_code = $this->localizationSettings->baseCurrency; if ($options['base_currency']) { //Allow to override it $iso_code = $options['base_currency']; diff --git a/src/Form/Type/LanguageMenuEntriesType.php b/src/Form/Type/LanguageMenuEntriesType.php new file mode 100644 index 000000000..a3bba77f5 --- /dev/null +++ b/src/Form/Type/LanguageMenuEntriesType.php @@ -0,0 +1,57 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Form\Type; + +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\LanguageType; +use Symfony\Component\Form\Extension\Core\Type\LocaleType; +use Symfony\Component\Intl\Languages; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class LanguageMenuEntriesType extends AbstractType +{ + public function __construct(#[Autowire(param: 'partdb.locale_menu')] private readonly array $preferred_languages) + { + + } + + public function getParent(): string + { + return LanguageType::class; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $choices = []; + foreach ($this->preferred_languages as $lang_code) { + $choices[Languages::getName($lang_code)] = $lang_code; + } + + $resolver->setDefaults([ + 'choice_loader' => null, + 'choices' => $choices, + ]); + } +} diff --git a/src/Form/Type/LocaleSelectType.php b/src/Form/Type/LocaleSelectType.php new file mode 100644 index 000000000..d47fb57fd --- /dev/null +++ b/src/Form/Type/LocaleSelectType.php @@ -0,0 +1,53 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Form\Type; + +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\LocaleType; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * A locale select field that uses the preferred languages from the configuration. + + */ +class LocaleSelectType extends AbstractType +{ + + public function __construct(#[Autowire(param: 'partdb.locale_menu')] private readonly array $preferred_languages) + { + + } + public function getParent(): string + { + return LocaleType::class; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'preferred_choices' => $this->preferred_languages, + ]); + } +} diff --git a/src/Form/Type/TriStateCheckboxType.php b/src/Form/Type/TriStateCheckboxType.php index 4523a839e..b2a85ad3f 100644 --- a/src/Form/Type/TriStateCheckboxType.php +++ b/src/Form/Type/TriStateCheckboxType.php @@ -100,7 +100,7 @@ public function buildView(FormView $view, FormInterface $form, array $options): * @return mixed The value in the transformed representation * */ - public function transform(mixed $value) + public function transform(mixed $value): mixed { if (true === $value) { return 'true'; @@ -142,7 +142,7 @@ public function transform(mixed $value) * * @return mixed The value in the original representation */ - public function reverseTransform(mixed $value) + public function reverseTransform(mixed $value): mixed { return match ($value) { 'true' => true, diff --git a/src/Form/UserAdminForm.php b/src/Form/UserAdminForm.php index 864bcf6b4..69be181f6 100644 --- a/src/Form/UserAdminForm.php +++ b/src/Form/UserAdminForm.php @@ -22,6 +22,7 @@ namespace App\Form; +use App\Form\Type\LocaleSelectType; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Base\AbstractNamedDBElement; use App\Entity\UserSystem\Group; @@ -35,7 +36,6 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\CollectionType; -use Symfony\Component\Form\Extension\Core\Type\LanguageType; use Symfony\Component\Form\Extension\Core\Type\PasswordType; use Symfony\Component\Form\Extension\Core\Type\RepeatedType; use Symfony\Component\Form\Extension\Core\Type\ResetType; @@ -140,11 +140,10 @@ public function buildForm(FormBuilderInterface $builder, array $options): void ]) //Config section - ->add('language', LanguageType::class, [ + ->add('language', LocaleSelectType::class, [ 'required' => false, 'placeholder' => 'user_settings.language.placeholder', 'label' => 'user.language_select', - 'preferred_choices' => ['en', 'de'], 'disabled' => !$this->security->isGranted('change_user_settings', $entity), ]) ->add('timezone', TimezoneType::class, [ diff --git a/src/Form/UserSettingsType.php b/src/Form/UserSettingsType.php index 05f63df43..0c7cb1697 100644 --- a/src/Form/UserSettingsType.php +++ b/src/Form/UserSettingsType.php @@ -22,6 +22,7 @@ namespace App\Form; +use App\Form\Type\LocaleSelectType; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\UserSystem\User; use App\Form\Type\CurrencyEntityType; @@ -33,7 +34,6 @@ use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\Extension\Core\Type\FileType; -use Symfony\Component\Form\Extension\Core\Type\LanguageType; use Symfony\Component\Form\Extension\Core\Type\ResetType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\TextType; @@ -47,7 +47,7 @@ class UserSettingsType extends AbstractType { public function __construct(protected Security $security, protected bool $demo_mode, - #[Autowire(param: 'partdb.locale_menu')] private readonly array $preferred_languages) + ) { } @@ -107,12 +107,11 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'mode' => 'markdown-full', 'disabled' => !$this->security->isGranted('edit_infos', $options['data']) || $this->demo_mode, ]) - ->add('language', LanguageType::class, [ + ->add('language', LocaleSelectType::class, [ 'disabled' => $this->demo_mode, 'required' => false, 'placeholder' => 'user_settings.language.placeholder', 'label' => 'user.language_select', - 'preferred_choices' => $this->preferred_languages, ]) ->add('timezone', TimezoneType::class, [ 'disabled' => $this->demo_mode, diff --git a/src/Migration/WithPermPresetsTrait.php b/src/Migration/WithPermPresetsTrait.php index 5182f3b6e..203ef68a7 100644 --- a/src/Migration/WithPermPresetsTrait.php +++ b/src/Migration/WithPermPresetsTrait.php @@ -26,7 +26,7 @@ use App\Entity\UserSystem\PermissionData; use App\Security\Interfaces\HasPermissionsInterface; use App\Services\UserSystem\PermissionPresetsHelper; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Psr\Container\ContainerInterface; trait WithPermPresetsTrait { @@ -62,7 +62,7 @@ public function getPermissions(): PermissionData return json_encode($user->getPermissions()); } - public function setContainer(ContainerInterface $container = null): void + public function setContainer(?ContainerInterface $container = null): void { if ($container !== null) { $this->container = $container; diff --git a/src/Repository/AttachmentRepository.php b/src/Repository/AttachmentRepository.php index 8acce3e3b..4fc0abc90 100644 --- a/src/Repository/AttachmentRepository.php +++ b/src/Repository/AttachmentRepository.php @@ -58,15 +58,15 @@ public function getPrivateAttachmentsCount(): int { $qb = $this->createQueryBuilder('attachment'); $qb->select('COUNT(attachment)') - ->where('attachment.path LIKE :like'); - $qb->setParameter('like', '\\%SECURE\\%%'); + ->where('attachment.internal_path LIKE :like ESCAPE \'#\''); + $qb->setParameter('like', '#%SECURE#%%'); $query = $qb->getQuery(); return (int) $query->getSingleScalarResult(); } /** - * Gets the count of all external attachments (attachments only containing a URL). + * Gets the count of all external attachments (attachments containing only an external path). * * @throws NoResultException * @throws NonUniqueResultException @@ -75,17 +75,16 @@ public function getExternalAttachments(): int { $qb = $this->createQueryBuilder('attachment'); $qb->select('COUNT(attachment)') - ->where('ILIKE(attachment.path, :http) = TRUE') - ->orWhere('ILIKE(attachment.path, :https) = TRUE'); - $qb->setParameter('http', 'http://%'); - $qb->setParameter('https', 'https://%'); + ->where('attachment.external_path IS NOT NULL') + ->andWhere('attachment.internal_path IS NULL'); + $query = $qb->getQuery(); return (int) $query->getSingleScalarResult(); } /** - * Gets the count of all attachments where a user uploaded a file. + * Gets the count of all attachments where a user uploaded a file or a file was downloaded from an external source. * * @throws NoResultException * @throws NonUniqueResultException @@ -94,12 +93,12 @@ public function getUserUploadedAttachments(): int { $qb = $this->createQueryBuilder('attachment'); $qb->select('COUNT(attachment)') - ->where('attachment.path LIKE :base') - ->orWhere('attachment.path LIKE :media') - ->orWhere('attachment.path LIKE :secure'); - $qb->setParameter('secure', '\\%SECURE\\%%'); - $qb->setParameter('base', '\\%BASE\\%%'); - $qb->setParameter('media', '\\%MEDIA\\%%'); + ->where('attachment.internal_path LIKE :base ESCAPE \'#\'') + ->orWhere('attachment.internal_path LIKE :media ESCAPE \'#\'') + ->orWhere('attachment.internal_path LIKE :secure ESCAPE \'#\''); + $qb->setParameter('secure', '#%SECURE#%%'); + $qb->setParameter('base', '#%BASE#%%'); + $qb->setParameter('media', '#%MEDIA#%%'); $query = $qb->getQuery(); return (int) $query->getSingleScalarResult(); diff --git a/src/Repository/LogEntryRepository.php b/src/Repository/LogEntryRepository.php index bf9909c5e..6850d06b3 100644 --- a/src/Repository/LogEntryRepository.php +++ b/src/Repository/LogEntryRepository.php @@ -160,7 +160,7 @@ public function getElementExistedAtTimestamp(AbstractDBElement $element, \DateTi * @param int|null $limit * @param int|null $offset */ - public function getLogsOrderedByTimestamp(string $order = 'DESC', int $limit = null, int $offset = null): array + public function getLogsOrderedByTimestamp(string $order = 'DESC', ?int $limit = null, ?int $offset = null): array { return $this->findBy([], ['timestamp' => $order], $limit, $offset); } diff --git a/src/Repository/Parts/PartCustomStateRepository.php b/src/Repository/Parts/PartCustomStateRepository.php new file mode 100644 index 000000000..d66221a24 --- /dev/null +++ b/src/Repository/Parts/PartCustomStateRepository.php @@ -0,0 +1,48 @@ +. + */ +namespace App\Repository\Parts; + +use App\Entity\Parts\PartCustomState; +use App\Repository\AbstractPartsContainingRepository; +use InvalidArgumentException; + +class PartCustomStateRepository extends AbstractPartsContainingRepository +{ + public function getParts(object $element, string $nameOrderDirection = "ASC"): array + { + if (!$element instanceof PartCustomState) { + throw new InvalidArgumentException('$element must be an PartCustomState!'); + } + + return $this->getPartsByField($element, $nameOrderDirection, 'partUnit'); + } + + public function getPartsCount(object $element): int + { + if (!$element instanceof PartCustomState) { + throw new InvalidArgumentException('$element must be an PartCustomState!'); + } + + return $this->getPartsCountByField($element, 'partUnit'); + } +} diff --git a/src/Security/ApiTokenAuthenticator.php b/src/Security/ApiTokenAuthenticator.php index 23ab68b98..a52b1f7ce 100644 --- a/src/Security/ApiTokenAuthenticator.php +++ b/src/Security/ApiTokenAuthenticator.php @@ -131,7 +131,7 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio /** * @see https://datatracker.ietf.org/doc/html/rfc6750#section-3 */ - private function getAuthenticateHeader(string $errorDescription = null): string + private function getAuthenticateHeader(?string $errorDescription = null): string { $data = [ 'realm' => $this->realm, diff --git a/src/Security/AuthenticationEntryPoint.php b/src/Security/AuthenticationEntryPoint.php index c26e7667e..41f624b29 100644 --- a/src/Security/AuthenticationEntryPoint.php +++ b/src/Security/AuthenticationEntryPoint.php @@ -47,7 +47,7 @@ public function __construct( ) { } - public function start(Request $request, AuthenticationException $authException = null): Response + public function start(Request $request, ?AuthenticationException $authException = null): Response { //Check if the request is an API request if ($this->isJSONRequest($request)) { diff --git a/src/Security/SamlUserFactory.php b/src/Security/SamlUserFactory.php index d5c681468..312be8590 100644 --- a/src/Security/SamlUserFactory.php +++ b/src/Security/SamlUserFactory.php @@ -116,10 +116,10 @@ public function mapSAMLAttributesToLocalGroup(array $attributes): ?Group * Maps a list of SAML roles to a local group ID. * The first available mapping will be used (so the order of the $map is important, first match wins). * @param array $roles The list of SAML roles - * @param array $map|null The mapping from SAML roles. If null, the global mapping will be used. + * @param array|null $map The mapping from SAML roles. If null, the global mapping will be used. * @return int|null The ID of the local group or null if no mapping was found. */ - public function mapSAMLRolesToLocalGroupID(array $roles, array $map = null): ?int + public function mapSAMLRolesToLocalGroupID(array $roles, ?array $map = null): ?int { $map ??= $this->saml_role_mapping; diff --git a/src/Security/TwoFactor/WebauthnKeyLastUseTwoFactorProvider.php b/src/Security/TwoFactor/WebauthnKeyLastUseTwoFactorProvider.php index 9bfa691de..4d1269d6d 100644 --- a/src/Security/TwoFactor/WebauthnKeyLastUseTwoFactorProvider.php +++ b/src/Security/TwoFactor/WebauthnKeyLastUseTwoFactorProvider.php @@ -33,6 +33,7 @@ use Symfony\Component\DependencyInjection\Attribute\AsDecorator; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated; +use Webauthn\PublicKeyCredential; /** * This class decorates the Webauthn TwoFactorProvider and adds additional logic which allows us to set a last used date @@ -88,10 +89,12 @@ public function getFormRenderer(): TwoFactorFormRendererInterface private function getWebauthnKeyFromCode(string $authenticationCode): ?WebauthnKey { - $publicKeyCredentialLoader = $this->webauthnProvider->getPublicKeyCredentialLoader(); + $serializer = $this->webauthnProvider->getWebauthnSerializer(); //Try to load the public key credential from the code - $publicKeyCredential = $publicKeyCredentialLoader->load($authenticationCode); + $publicKeyCredential = $serializer->deserialize($authenticationCode, PublicKeyCredential::class, 'json', [ + 'json_decode_options' => JSON_THROW_ON_ERROR + ]); //Find the credential source for the given credential id $publicKeyCredentialSource = $this->publicKeyCredentialSourceRepository->findOneByCredentialId($publicKeyCredential->rawId); @@ -103,4 +106,4 @@ private function getWebauthnKeyFromCode(string $authenticationCode): ?WebauthnKe return $publicKeyCredentialSource; } -} \ No newline at end of file +} diff --git a/src/Security/UserChecker.php b/src/Security/UserChecker.php index 16afb37ef..239a60962 100644 --- a/src/Security/UserChecker.php +++ b/src/Security/UserChecker.php @@ -23,6 +23,7 @@ namespace App\Security; use App\Entity\UserSystem\User; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AccountStatusException; use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException; use Symfony\Component\Security\Core\User\UserCheckerInterface; @@ -51,7 +52,7 @@ public function checkPreAuth(UserInterface $user): void * * @throws AccountStatusException */ - public function checkPostAuth(UserInterface $user): void + public function checkPostAuth(UserInterface $user, ?TokenInterface $token = null): void { if (!$user instanceof User) { return; diff --git a/src/Security/Voter/AttachmentVoter.php b/src/Security/Voter/AttachmentVoter.php index c2b170538..df3d73a72 100644 --- a/src/Security/Voter/AttachmentVoter.php +++ b/src/Security/Voter/AttachmentVoter.php @@ -22,6 +22,7 @@ namespace App\Security\Voter; +use App\Entity\Attachments\PartCustomStateAttachment; use App\Services\UserSystem\VoterHelper; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Attachments\AttachmentContainingDBElement; @@ -41,6 +42,7 @@ use RuntimeException; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; use function in_array; @@ -56,7 +58,7 @@ public function __construct(private readonly Security $security, private readonl { } - protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { //This voter only works for attachments @@ -65,7 +67,8 @@ protected function voteOnAttribute(string $attribute, $subject, TokenInterface $ } if ($attribute === 'show_private') { - return $this->helper->isGranted($token, 'attachments', 'show_private'); + $vote?->addReason('User is not allowed to view private attachments.'); + return $this->helper->isGranted($token, 'attachments', 'show_private', $vote); } @@ -97,6 +100,8 @@ protected function voteOnAttribute(string $attribute, $subject, TokenInterface $ $param = 'measurement_units'; } elseif (is_a($subject, PartAttachment::class, true)) { $param = 'parts'; + } elseif (is_a($subject, PartCustomStateAttachment::class, true)) { + $param = 'part_custom_states'; } elseif (is_a($subject, StorageLocationAttachment::class, true)) { $param = 'storelocations'; } elseif (is_a($subject, SupplierAttachment::class, true)) { @@ -111,7 +116,8 @@ protected function voteOnAttribute(string $attribute, $subject, TokenInterface $ throw new RuntimeException('Encountered unknown Parameter type: ' . $subject); } - return $this->helper->isGranted($token, $param, $this->mapOperation($attribute)); + $vote?->addReason('User is not allowed to '.$this->mapOperation($attribute).' attachments of type '.$param.'.'); + return $this->helper->isGranted($token, $param, $this->mapOperation($attribute), $vote); } return false; diff --git a/src/Security/Voter/BOMEntryVoter.php b/src/Security/Voter/BOMEntryVoter.php index 121c81729..4ce40d47e 100644 --- a/src/Security/Voter/BOMEntryVoter.php +++ b/src/Security/Voter/BOMEntryVoter.php @@ -27,6 +27,7 @@ use App\Entity\ProjectSystem\ProjectBOMEntry; use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** @@ -46,7 +47,7 @@ protected function supports(string $attribute, mixed $subject): bool return $this->supportsAttribute($attribute) && is_a($subject, ProjectBOMEntry::class, true); } - protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool { if (!is_a($subject, ProjectBOMEntry::class, true)) { return false; @@ -87,4 +88,4 @@ public function supportsType(string $subjectType): bool { return $subjectType === 'string' || is_a($subjectType, ProjectBOMEntry::class, true); } -} \ No newline at end of file +} diff --git a/src/Security/Voter/GroupVoter.php b/src/Security/Voter/GroupVoter.php index 34839d385..f2ce6953b 100644 --- a/src/Security/Voter/GroupVoter.php +++ b/src/Security/Voter/GroupVoter.php @@ -25,6 +25,7 @@ use App\Entity\UserSystem\Group; use App\Services\UserSystem\VoterHelper; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** @@ -43,9 +44,9 @@ public function __construct(private readonly VoterHelper $helper) * * @param string $attribute */ - protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { - return $this->helper->isGranted($token, 'groups', $attribute); + return $this->helper->isGranted($token, 'groups', $attribute, $vote); } /** diff --git a/src/Security/Voter/HasAccessPermissionsVoter.php b/src/Security/Voter/HasAccessPermissionsVoter.php index bd466d073..9adef9778 100644 --- a/src/Security/Voter/HasAccessPermissionsVoter.php +++ b/src/Security/Voter/HasAccessPermissionsVoter.php @@ -26,6 +26,7 @@ use App\Services\UserSystem\PermissionManager; use App\Services\UserSystem\VoterHelper; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** @@ -41,7 +42,7 @@ public function __construct(private readonly PermissionManager $permissionManage { } - protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { $user = $this->helper->resolveUser($token); return $this->permissionManager->hasAnyPermissionSetToAllowInherited($user); @@ -56,4 +57,4 @@ public function supportsAttribute(string $attribute): bool { return $attribute === self::ROLE; } -} \ No newline at end of file +} diff --git a/src/Security/Voter/ImpersonateUserVoter.php b/src/Security/Voter/ImpersonateUserVoter.php index edf55c62d..1f8a70c60 100644 --- a/src/Security/Voter/ImpersonateUserVoter.php +++ b/src/Security/Voter/ImpersonateUserVoter.php @@ -26,6 +26,7 @@ use App\Entity\UserSystem\User; use App\Services\UserSystem\VoterHelper; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\User\UserInterface; @@ -47,9 +48,16 @@ protected function supports(string $attribute, mixed $subject): bool && $subject instanceof UserInterface; } - protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool { - return $this->helper->isGranted($token, 'users', 'impersonate'); + $result = $this->helper->isGranted($token, 'users', 'impersonate'); + + if ($result === false) { + $vote?->addReason('User is not allowed to impersonate other users.'); + $this->helper->addReason($vote, 'users', 'impersonate'); + } + + return $result; } public function supportsAttribute(string $attribute): bool @@ -61,4 +69,4 @@ public function supportsType(string $subjectType): bool { return is_a($subjectType, User::class, true); } -} \ No newline at end of file +} diff --git a/src/Security/Voter/LabelProfileVoter.php b/src/Security/Voter/LabelProfileVoter.php index 47505bf9c..1687bf456 100644 --- a/src/Security/Voter/LabelProfileVoter.php +++ b/src/Security/Voter/LabelProfileVoter.php @@ -44,6 +44,7 @@ use App\Entity\LabelSystem\LabelProfile; use App\Services\UserSystem\VoterHelper; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** @@ -58,14 +59,15 @@ final class LabelProfileVoter extends Voter 'delete' => 'delete_profiles', 'show_history' => 'show_history', 'revert_element' => 'revert_element', + 'import' => 'import', ]; public function __construct(private readonly VoterHelper $helper) {} - protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { - return $this->helper->isGranted($token, 'labels', self::MAPPING[$attribute]); + return $this->helper->isGranted($token, 'labels', self::MAPPING[$attribute], $vote); } protected function supports($attribute, $subject): bool diff --git a/src/Security/Voter/LogEntryVoter.php b/src/Security/Voter/LogEntryVoter.php index 08bc3b703..dcb75a7ae 100644 --- a/src/Security/Voter/LogEntryVoter.php +++ b/src/Security/Voter/LogEntryVoter.php @@ -26,6 +26,7 @@ use Symfony\Bundle\SecurityBundle\Security; use App\Entity\LogSystem\AbstractLogEntry; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** @@ -39,7 +40,7 @@ public function __construct(private readonly Security $security, private readonl { } - protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { $user = $this->helper->resolveUser($token); @@ -48,19 +49,19 @@ protected function voteOnAttribute(string $attribute, $subject, TokenInterface $ } if ('delete' === $attribute) { - return $this->helper->isGranted($token, 'system', 'delete_logs'); + return $this->helper->isGranted($token, 'system', 'delete_logs', $vote); } if ('read' === $attribute) { //Allow read of the users own log entries if ( $subject->getUser() === $user - && $this->helper->isGranted($token, 'self', 'show_logs') + && $this->helper->isGranted($token, 'self', 'show_logs', $vote) ) { return true; } - return $this->helper->isGranted($token, 'system', 'show_logs'); + return $this->helper->isGranted($token, 'system', 'show_logs', $vote); } if ('show_details' === $attribute) { diff --git a/src/Security/Voter/OrderdetailVoter.php b/src/Security/Voter/OrderdetailVoter.php index 20843b9ad..3bb2a3a3a 100644 --- a/src/Security/Voter/OrderdetailVoter.php +++ b/src/Security/Voter/OrderdetailVoter.php @@ -46,6 +46,7 @@ use App\Entity\Parts\Part; use App\Entity\PriceInformations\Orderdetail; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** @@ -59,7 +60,7 @@ public function __construct(private readonly Security $security, private readonl protected const ALLOWED_PERMS = ['read', 'edit', 'create', 'delete', 'show_history', 'revert_element']; - protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { if (! is_a($subject, Orderdetail::class, true)) { throw new \RuntimeException('This voter can only handle Orderdetail objects!'); @@ -75,7 +76,7 @@ protected function voteOnAttribute(string $attribute, $subject, TokenInterface $ //If we have no part associated use the generic part permission if (is_string($subject) || !$subject->getPart() instanceof Part) { - return $this->helper->isGranted($token, 'parts', $operation); + return $this->helper->isGranted($token, 'parts', $operation, $vote); } //Otherwise vote on the part diff --git a/src/Security/Voter/ParameterVoter.php b/src/Security/Voter/ParameterVoter.php index 8ee2b9f59..5dc30ea25 100644 --- a/src/Security/Voter/ParameterVoter.php +++ b/src/Security/Voter/ParameterVoter.php @@ -22,6 +22,7 @@ */ namespace App\Security\Voter; +use App\Entity\Parameters\PartCustomStateParameter; use App\Services\UserSystem\VoterHelper; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Base\AbstractDBElement; @@ -39,6 +40,7 @@ use App\Entity\Parameters\SupplierParameter; use RuntimeException; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** @@ -53,7 +55,7 @@ public function __construct(private readonly Security $security, private readonl { } - protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { //return $this->resolver->inherit($user, 'attachments', $attribute) ?? false; @@ -96,6 +98,8 @@ protected function voteOnAttribute(string $attribute, $subject, TokenInterface $ $param = 'measurement_units'; } elseif (is_a($subject, PartParameter::class, true)) { $param = 'parts'; + } elseif (is_a($subject, PartCustomStateParameter::class, true)) { + $param = 'part_custom_states'; } elseif (is_a($subject, StorageLocationParameter::class, true)) { $param = 'storelocations'; } elseif (is_a($subject, SupplierParameter::class, true)) { @@ -108,7 +112,7 @@ protected function voteOnAttribute(string $attribute, $subject, TokenInterface $ throw new RuntimeException('Encountered unknown Parameter type: ' . (is_object($subject) ? $subject::class : $subject)); } - return $this->helper->isGranted($token, $param, $attribute); + return $this->helper->isGranted($token, $param, $attribute, $vote); } protected function supports(string $attribute, $subject): bool diff --git a/src/Security/Voter/PartAssociationVoter.php b/src/Security/Voter/PartAssociationVoter.php index 7678b67ae..f1eb83c79 100644 --- a/src/Security/Voter/PartAssociationVoter.php +++ b/src/Security/Voter/PartAssociationVoter.php @@ -46,6 +46,7 @@ use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Parts\Part; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** @@ -61,7 +62,7 @@ public function __construct(private readonly Security $security, private readonl protected const ALLOWED_PERMS = ['read', 'edit', 'create', 'delete', 'show_history', 'revert_element']; - protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { if (!is_string($subject) && !$subject instanceof PartAssociation) { throw new \RuntimeException('Invalid subject type!'); @@ -77,7 +78,7 @@ protected function voteOnAttribute(string $attribute, $subject, TokenInterface $ //If we have no part associated use the generic part permission if (is_string($subject) || !$subject->getOwner() instanceof Part) { - return $this->helper->isGranted($token, 'parts', $operation); + return $this->helper->isGranted($token, 'parts', $operation, $vote); } //Otherwise vote on the part diff --git a/src/Security/Voter/PartLotVoter.php b/src/Security/Voter/PartLotVoter.php index a64473c86..87c3d1352 100644 --- a/src/Security/Voter/PartLotVoter.php +++ b/src/Security/Voter/PartLotVoter.php @@ -46,6 +46,7 @@ use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** @@ -59,13 +60,13 @@ public function __construct(private readonly Security $security, private readonl protected const ALLOWED_PERMS = ['read', 'edit', 'create', 'delete', 'show_history', 'revert_element', 'withdraw', 'add', 'move']; - protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { $user = $this->helper->resolveUser($token); if (in_array($attribute, ['withdraw', 'add', 'move'], true)) { - $base_permission = $this->helper->isGranted($token, 'parts_stock', $attribute); + $base_permission = $this->helper->isGranted($token, 'parts_stock', $attribute, $vote); $lot_permission = true; //If the lot has an owner, we need to check if the user is the owner of the lot to be allowed to withdraw it. @@ -73,6 +74,10 @@ protected function voteOnAttribute(string $attribute, $subject, TokenInterface $ $lot_permission = $subject->getOwner() === $user || $subject->getOwner()->getID() === $user->getID(); } + if (!$lot_permission) { + $vote->addReason('User is not the owner of the lot.'); + } + return $base_permission && $lot_permission; } @@ -86,7 +91,7 @@ protected function voteOnAttribute(string $attribute, $subject, TokenInterface $ //If we have no part associated use the generic part permission if (is_string($subject) || !$subject->getPart() instanceof Part) { - return $this->helper->isGranted($token, 'parts', $operation); + return $this->helper->isGranted($token, 'parts', $operation, $vote); } //Otherwise vote on the part diff --git a/src/Security/Voter/PartVoter.php b/src/Security/Voter/PartVoter.php index ef70b6cef..159e6893d 100644 --- a/src/Security/Voter/PartVoter.php +++ b/src/Security/Voter/PartVoter.php @@ -25,6 +25,7 @@ use App\Entity\Parts\Part; use App\Services\UserSystem\VoterHelper; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** @@ -52,10 +53,9 @@ protected function supports($attribute, $subject): bool return false; } - protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { - //Null concealing operator means, that no - return $this->helper->isGranted($token, 'parts', $attribute); + return $this->helper->isGranted($token, 'parts', $attribute, $vote); } public function supportsAttribute(string $attribute): bool diff --git a/src/Security/Voter/PermissionVoter.php b/src/Security/Voter/PermissionVoter.php index c6ec1b3df..8c304d866 100644 --- a/src/Security/Voter/PermissionVoter.php +++ b/src/Security/Voter/PermissionVoter.php @@ -24,6 +24,7 @@ use App\Services\UserSystem\VoterHelper; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** @@ -39,12 +40,17 @@ public function __construct(private readonly VoterHelper $helper) } - protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool { $attribute = ltrim($attribute, '@'); [$perm, $op] = explode('.', $attribute); - return $this->helper->isGranted($token, $perm, $op); + $result = $this->helper->isGranted($token, $perm, $op); + if ($result === false) { + $this->helper->addReason($vote, $perm, $op); + } + + return $result; } public function supportsAttribute(string $attribute): bool diff --git a/src/Security/Voter/PricedetailVoter.php b/src/Security/Voter/PricedetailVoter.php index 681b73b73..ca86f1ce0 100644 --- a/src/Security/Voter/PricedetailVoter.php +++ b/src/Security/Voter/PricedetailVoter.php @@ -47,6 +47,7 @@ use App\Entity\Parts\Part; use App\Entity\PriceInformations\Pricedetail; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** @@ -60,7 +61,7 @@ public function __construct(private readonly Security $security, private readonl protected const ALLOWED_PERMS = ['read', 'edit', 'create', 'delete', 'show_history', 'revert_element']; - protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { $operation = match ($attribute) { 'read' => 'read', @@ -72,7 +73,7 @@ protected function voteOnAttribute(string $attribute, $subject, TokenInterface $ //If we have no part associated use the generic part permission if (is_string($subject) || !$subject->getOrderdetail() instanceof Orderdetail || !$subject->getOrderdetail()->getPart() instanceof Part) { - return $this->helper->isGranted($token, 'parts', $operation); + return $this->helper->isGranted($token, 'parts', $operation, $vote); } //Otherwise vote on the part diff --git a/src/Security/Voter/StructureVoter.php b/src/Security/Voter/StructureVoter.php index 2417b796d..16d38e058 100644 --- a/src/Security/Voter/StructureVoter.php +++ b/src/Security/Voter/StructureVoter.php @@ -23,6 +23,7 @@ namespace App\Security\Voter; use App\Entity\Attachments\AttachmentType; +use App\Entity\Parts\PartCustomState; use App\Entity\ProjectSystem\Project; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; @@ -33,6 +34,7 @@ use App\Entity\PriceInformations\Currency; use App\Services\UserSystem\VoterHelper; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; use function is_object; @@ -52,6 +54,7 @@ final class StructureVoter extends Voter Supplier::class => 'suppliers', Currency::class => 'currencies', MeasurementUnit::class => 'measurement_units', + PartCustomState::class => 'part_custom_states', ]; public function __construct(private readonly VoterHelper $helper) @@ -113,10 +116,10 @@ protected function instanceToPermissionName(object|string $subject): ?string * * @param string $attribute */ - protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { $permission_name = $this->instanceToPermissionName($subject); //Just resolve the permission - return $this->helper->isGranted($token, $permission_name, $attribute); + return $this->helper->isGranted($token, $permission_name, $attribute, $vote); } } diff --git a/src/Security/Voter/UserVoter.php b/src/Security/Voter/UserVoter.php index b41c1a40f..97f8e4fbe 100644 --- a/src/Security/Voter/UserVoter.php +++ b/src/Security/Voter/UserVoter.php @@ -26,6 +26,7 @@ use App\Services\UserSystem\PermissionManager; use App\Services\UserSystem\VoterHelper; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; use function in_array; @@ -79,7 +80,7 @@ public function supportsType(string $subjectType): bool * * @param string $attribute */ - protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { $user = $this->helper->resolveUser($token); @@ -97,7 +98,7 @@ protected function voteOnAttribute(string $attribute, $subject, TokenInterface $ if (($subject instanceof User) && $subject->getID() === $user->getID() && $this->helper->isValidOperation('self', $attribute)) { //Then we also need to check the self permission - $tmp = $this->helper->isGranted($token, 'self', $attribute); + $tmp = $this->helper->isGranted($token, 'self', $attribute, $vote); //But if the self value is not allowed then use just the user value: if ($tmp) { return $tmp; @@ -106,7 +107,7 @@ protected function voteOnAttribute(string $attribute, $subject, TokenInterface $ //Else just check user permission: if ($this->helper->isValidOperation('users', $attribute)) { - return $this->helper->isGranted($token, 'users', $attribute); + return $this->helper->isGranted($token, 'users', $attribute, $vote); } return false; diff --git a/src/Serializer/APIPlatform/DetermineTypeFromElementIRIDenormalizer.php b/src/Serializer/APIPlatform/DetermineTypeFromElementIRIDenormalizer.php index 8283dbbe5..78679214f 100644 --- a/src/Serializer/APIPlatform/DetermineTypeFromElementIRIDenormalizer.php +++ b/src/Serializer/APIPlatform/DetermineTypeFromElementIRIDenormalizer.php @@ -24,7 +24,7 @@ namespace App\Serializer\APIPlatform; use ApiPlatform\Metadata\Exception\ResourceClassNotFoundException; -use ApiPlatform\Api\IriConverterInterface; +use ApiPlatform\Metadata\IriConverterInterface; use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; @@ -121,4 +121,4 @@ public function getSupportedTypes(?string $format): array return $tmp; } -} \ No newline at end of file +} diff --git a/src/Serializer/AttachmentNormalizer.php b/src/Serializer/AttachmentNormalizer.php index bb167fc6f..bd791d04c 100644 --- a/src/Serializer/AttachmentNormalizer.php +++ b/src/Serializer/AttachmentNormalizer.php @@ -42,7 +42,7 @@ public function __construct( { } - public function normalize(mixed $object, string $format = null, array $context = []): array|null + public function normalize(mixed $object, ?string $format = null, array $context = []): array|null { if (!$object instanceof Attachment) { throw new \InvalidArgumentException('This normalizer only supports Attachment objects!'); @@ -52,15 +52,19 @@ public function normalize(mixed $object, string $format = null, array $context = $context[self::ALREADY_CALLED] = true; $data = $this->normalizer->normalize($object, $format, $context); + $data['internal_path'] = $this->attachmentURLGenerator->getInternalViewURL($object); - $data['media_url'] = $this->attachmentURLGenerator->getViewURL($object); //Add thumbnail url if the attachment is a picture $data['thumbnail_url'] = $object->isPicture() ? $this->attachmentURLGenerator->getThumbnailURL($object) : null; + //For backwards compatibility reasons + //Deprecated: Use internal_path and external_path instead + $data['media_url'] = $data['internal_path'] ?? $object->getExternalPath(); + return $data; } - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { // avoid recursion: only call once per object if (isset($context[self::ALREADY_CALLED])) { diff --git a/src/Serializer/BigNumberNormalizer.php b/src/Serializer/BigNumberNormalizer.php index 8ef06d679..10cedfa55 100644 --- a/src/Serializer/BigNumberNormalizer.php +++ b/src/Serializer/BigNumberNormalizer.php @@ -33,12 +33,12 @@ class BigNumberNormalizer implements NormalizerInterface, DenormalizerInterface { - public function supportsNormalization($data, string $format = null, array $context = []): bool + public function supportsNormalization($data, ?string $format = null, array $context = []): bool { return $data instanceof BigNumber; } - public function normalize($object, string $format = null, array $context = []): string + public function normalize($object, ?string $format = null, array $context = []): string { if (!$object instanceof BigNumber) { throw new \InvalidArgumentException('This normalizer only supports BigNumber objects!'); @@ -58,7 +58,7 @@ public function getSupportedTypes(?string $format): array ]; } - public function denormalize(mixed $data, string $type, string $format = null, array $context = []): BigNumber|null + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): BigNumber|null { if (!is_a($type, BigNumber::class, true)) { throw new \InvalidArgumentException('This normalizer only supports BigNumber objects!'); @@ -67,7 +67,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar return $type::of($data); } - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { //data must be a string or a number (int, float, etc.) and the type must be BigNumber or BigDecimal return (is_string($data) || is_numeric($data)) && (is_subclass_of($type, BigNumber::class)); diff --git a/src/Serializer/PartNormalizer.php b/src/Serializer/PartNormalizer.php index e527548fc..775df77f0 100644 --- a/src/Serializer/PartNormalizer.php +++ b/src/Serializer/PartNormalizer.php @@ -63,13 +63,13 @@ public function __construct( { } - public function supportsNormalization($data, string $format = null, array $context = []): bool + public function supportsNormalization($data, ?string $format = null, array $context = []): bool { //We only remove the type field for CSV export return !isset($context[self::ALREADY_CALLED]) && $format === 'csv' && $data instanceof Part ; } - public function normalize($object, string $format = null, array $context = []): array + public function normalize($object, ?string $format = null, array $context = []): array { if (!$object instanceof Part) { throw new \InvalidArgumentException('This normalizer only supports Part objects!'); @@ -92,7 +92,7 @@ public function normalize($object, string $format = null, array $context = []): return $data; } - public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization($data, string $type, ?string $format = null, array $context = []): bool { //Only denormalize if we are doing a file import operation if (!($context['partdb_import'] ?? false)) { @@ -117,7 +117,7 @@ private function normalizeKeys(array &$data): array return $data; } - public function denormalize($data, string $type, string $format = null, array $context = []): ?Part + public function denormalize($data, string $type, ?string $format = null, array $context = []): ?Part { $this->normalizeKeys($data); diff --git a/src/Serializer/StructuralElementDenormalizer.php b/src/Serializer/StructuralElementDenormalizer.php index 92af8db34..9f4256f93 100644 --- a/src/Serializer/StructuralElementDenormalizer.php +++ b/src/Serializer/StructuralElementDenormalizer.php @@ -49,7 +49,7 @@ public function __construct( { } - public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization($data, string $type, ?string $format = null, array $context = []): bool { //Only denormalize if we are doing a file import operation if (!($context['partdb_import'] ?? false)) { @@ -78,7 +78,7 @@ public function supportsDenormalization($data, string $type, string $format = nu * @return AbstractStructuralDBElement|null * @phpstan-return T|null */ - public function denormalize($data, string $type, string $format = null, array $context = []): ?AbstractStructuralDBElement + 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; @@ -122,7 +122,7 @@ public function denormalize($data, string $type, string $format = null, array $c return $deserialized_entity; } - public function getSupportedTypes(): array + public function getSupportedTypes(?string $format): array { //Must be false, because we use in_array in supportsDenormalization return [ diff --git a/src/Serializer/StructuralElementFromNameDenormalizer.php b/src/Serializer/StructuralElementFromNameDenormalizer.php index 92da353f0..1d7255b78 100644 --- a/src/Serializer/StructuralElementFromNameDenormalizer.php +++ b/src/Serializer/StructuralElementFromNameDenormalizer.php @@ -36,7 +36,7 @@ public function __construct(private readonly EntityManagerInterface $em) { } - public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization($data, string $type, ?string $format = null, array $context = []): bool { //Only denormalize if we are doing a file import operation if (!($context['partdb_import'] ?? false)) { @@ -51,7 +51,7 @@ public function supportsDenormalization($data, string $type, string $format = nu * @phpstan-param class-string $type * @phpstan-return T|null */ - public function denormalize($data, string $type, string $format = null, array $context = []): AbstractStructuralDBElement|null + public function denormalize($data, string $type, ?string $format = null, array $context = []): AbstractStructuralDBElement|null { //Retrieve the repository for the given type /** @var StructuralDBElementRepository $repo */ diff --git a/src/Serializer/StructuralElementNormalizer.php b/src/Serializer/StructuralElementNormalizer.php index 1838f2105..bf3e1097a 100644 --- a/src/Serializer/StructuralElementNormalizer.php +++ b/src/Serializer/StructuralElementNormalizer.php @@ -23,37 +23,48 @@ namespace App\Serializer; use App\Entity\Base\AbstractStructuralDBElement; +use App\Serializer\APIPlatform\SkippableItemNormalizer; use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; /** * @see \App\Tests\Serializer\StructuralElementNormalizerTest */ -class StructuralElementNormalizer implements NormalizerInterface +class StructuralElementNormalizer implements NormalizerInterface, NormalizerAwareInterface { - public function __construct( - #[Autowire(service: ObjectNormalizer::class)]private readonly NormalizerInterface $normalizer - ) - { - } + use NormalizerAwareTrait; - public function supportsNormalization($data, string $format = null, array $context = []): bool + public const ALREADY_CALLED = 'STRUCTURAL_ELEMENT_NORMALIZER_ALREADY_CALLED'; + + public function supportsNormalization($data, ?string $format = null, array $context = []): bool { //Only normalize if we are doing a file export operation if (!($context['partdb_export'] ?? false)) { return false; } + if (isset($context[self::ALREADY_CALLED]) && in_array($data, $context[self::ALREADY_CALLED], true)) { + //If we already handled this object, skip it + return false; + } + return $data instanceof AbstractStructuralDBElement; } - public function normalize($object, string $format = null, array $context = []): mixed + public function normalize($object, ?string $format = null, array $context = []): \ArrayObject|bool|float|int|string|array { if (!$object instanceof AbstractStructuralDBElement) { throw new \InvalidArgumentException('This normalizer only supports AbstractStructural objects!'); } + //Avoid infinite recursion by checking if we already handled this object + $context[self::ALREADY_CALLED] = $context[self::ALREADY_CALLED] ?? []; + $context[SkippableItemNormalizer::DISABLE_ITEM_NORMALIZER] = true; + $context[self::ALREADY_CALLED][] = $object; + $data = $this->normalizer->normalize($object, $format, $context); //If the data is not an array, we can't do anything with it @@ -77,7 +88,8 @@ public function normalize($object, string $format = null, array $context = []): public function getSupportedTypes(?string $format): array { return [ - AbstractStructuralDBElement::class => true, + //We cannot cache the result, as it depends on the context + AbstractStructuralDBElement::class => false, ]; } } diff --git a/src/Services/Attachments/AttachmentManager.php b/src/Services/Attachments/AttachmentManager.php index 4429179ed..1075141b6 100644 --- a/src/Services/Attachments/AttachmentManager.php +++ b/src/Services/Attachments/AttachmentManager.php @@ -44,35 +44,31 @@ public function __construct(protected AttachmentPathResolver $pathResolver) * * @param Attachment $attachment The attachment for which the file should be generated * - * @return SplFileInfo|null The fileinfo for the attachment file. Null, if the attachment is external or has + * @return SplFileInfo|null The fileinfo for the attachment file. Null, if the attachment is only external or has * invalid file. */ public function attachmentToFile(Attachment $attachment): ?SplFileInfo { - if ($attachment->isExternal() || !$this->isFileExisting($attachment)) { + if (!$this->isInternalFileExisting($attachment)) { return null; } - return new SplFileInfo($this->toAbsoluteFilePath($attachment)); + return new SplFileInfo($this->toAbsoluteInternalFilePath($attachment)); } /** - * Returns the absolute filepath of the attachment. Null is returned, if the attachment is externally saved, - * or is not existing. + * Returns the absolute filepath to the internal copy of the attachment. Null is returned, if the attachment is + * only externally saved, or is not existing. * * @param Attachment $attachment The attachment for which the filepath should be determined */ - public function toAbsoluteFilePath(Attachment $attachment): ?string + public function toAbsoluteInternalFilePath(Attachment $attachment): ?string { - if ($attachment->getPath() === '') { + if (!$attachment->hasInternal()){ return null; } - if ($attachment->isExternal()) { - return null; - } - - $path = $this->pathResolver->placeholderToRealPath($attachment->getPath()); + $path = $this->pathResolver->placeholderToRealPath($attachment->getInternalPath()); //realpath does not work with null as argument if (null === $path) { @@ -89,8 +85,8 @@ public function toAbsoluteFilePath(Attachment $attachment): ?string } /** - * Checks if the file in this attachement is existing. This works for files on the HDD, and for URLs - * (it's not checked if the ressource behind the URL is really existing, so for every external attachment true is returned). + * Checks if the file in this attachment is existing. This works for files on the HDD, and for URLs + * (it's not checked if the resource behind the URL is really existing, so for every external attachment true is returned). * * @param Attachment $attachment The attachment for which the existence should be checked * @@ -98,15 +94,23 @@ public function toAbsoluteFilePath(Attachment $attachment): ?string */ public function isFileExisting(Attachment $attachment): bool { - if ($attachment->getPath() === '') { - return false; - } - - if ($attachment->isExternal()) { + if($attachment->hasExternal()){ return true; } + return $this->isInternalFileExisting($attachment); + } - $absolute_path = $this->toAbsoluteFilePath($attachment); + /** + * Checks if the internal file in this attachment is existing. Returns false if the attachment doesn't have an + * internal file. + * + * @param Attachment $attachment The attachment for which the existence should be checked + * + * @return bool true if the file is existing + */ + public function isInternalFileExisting(Attachment $attachment): bool + { + $absolute_path = $this->toAbsoluteInternalFilePath($attachment); if (null === $absolute_path) { return false; @@ -117,21 +121,17 @@ public function isFileExisting(Attachment $attachment): bool /** * Returns the filesize of the attachments in bytes. - * For external attachments or not existing attachments, null is returned. + * For purely external attachments or inexistent attachments, null is returned. * * @param Attachment $attachment the filesize for which the filesize should be calculated */ public function getFileSize(Attachment $attachment): ?int { - if ($attachment->isExternal()) { - return null; - } - - if (!$this->isFileExisting($attachment)) { + if (!$this->isInternalFileExisting($attachment)) { return null; } - $tmp = filesize($this->toAbsoluteFilePath($attachment)); + $tmp = filesize($this->toAbsoluteInternalFilePath($attachment)); return false !== $tmp ? $tmp : null; } diff --git a/src/Services/Attachments/AttachmentPathResolver.php b/src/Services/Attachments/AttachmentPathResolver.php index e3e7a3ca3..1b52c89b4 100644 --- a/src/Services/Attachments/AttachmentPathResolver.php +++ b/src/Services/Attachments/AttachmentPathResolver.php @@ -115,12 +115,16 @@ public function parameterToAbsolutePath(?string $param_path): ?string * Converts an relative placeholder filepath (with %MEDIA% or older %BASE%) to an absolute filepath on disk. * The directory separator is always /. Relative pathes are not realy possible (.. is striped). * - * @param string $placeholder_path the filepath with placeholder for which the real path should be determined + * @param string|null $placeholder_path the filepath with placeholder for which the real path should be determined * * @return string|null The absolute real path of the file, or null if the placeholder path is invalid */ - public function placeholderToRealPath(string $placeholder_path): ?string + public function placeholderToRealPath(?string $placeholder_path): ?string { + if (null === $placeholder_path) { + return null; + } + //The new attachments use %MEDIA% as placeholders, which is the directory set in media_directory //Older path entries are given via %BASE% which was the project root diff --git a/src/Services/Attachments/AttachmentReverseSearch.php b/src/Services/Attachments/AttachmentReverseSearch.php index 5f4f86de6..e05192d0d 100644 --- a/src/Services/Attachments/AttachmentReverseSearch.php +++ b/src/Services/Attachments/AttachmentReverseSearch.php @@ -55,7 +55,7 @@ public function findAttachmentsByFile(SplFileInfo $file): array $repo = $this->em->getRepository(Attachment::class); return $repo->findBy([ - 'path' => [$relative_path_new, $relative_path_old], + 'internal_path' => [$relative_path_new, $relative_path_old], ]); } diff --git a/src/Services/Attachments/AttachmentSubmitHandler.php b/src/Services/Attachments/AttachmentSubmitHandler.php index d9b2a3805..c7e69257e 100644 --- a/src/Services/Attachments/AttachmentSubmitHandler.php +++ b/src/Services/Attachments/AttachmentSubmitHandler.php @@ -30,6 +30,7 @@ use App\Entity\Attachments\CategoryAttachment; use App\Entity\Attachments\CurrencyAttachment; use App\Entity\Attachments\LabelAttachment; +use App\Entity\Attachments\PartCustomStateAttachment; use App\Entity\Attachments\ProjectAttachment; use App\Entity\Attachments\FootprintAttachment; use App\Entity\Attachments\GroupAttachment; @@ -40,6 +41,7 @@ use App\Entity\Attachments\SupplierAttachment; use App\Entity\Attachments\UserAttachment; use App\Exceptions\AttachmentDownloadException; +use App\Settings\SystemSettings\AttachmentsSettings; use Hshn\Base64EncodedFile\HttpFoundation\File\Base64EncodedFile; use Hshn\Base64EncodedFile\HttpFoundation\File\UploadedBase64EncodedFile; use const DIRECTORY_SEPARATOR; @@ -56,6 +58,9 @@ */ class AttachmentSubmitHandler { + /** + * @var array The mapping used to determine which folder will be used for an attachment type + */ protected array $folder_mapping; private ?int $max_upload_size_bytes = null; @@ -64,16 +69,19 @@ class AttachmentSubmitHandler 'asp', 'cgi', 'py', 'pl', 'exe', 'aspx', 'js', 'mjs', 'jsp', 'css', 'jar', 'html', 'htm', 'shtm', 'shtml', 'htaccess', 'htpasswd', '']; - public function __construct(protected AttachmentPathResolver $pathResolver, protected bool $allow_attachments_downloads, - protected HttpClientInterface $httpClient, protected MimeTypesInterface $mimeTypes, - protected FileTypeFilterTools $filterTools, /** - * @var string The user configured maximum upload size. This is a string like "10M" or "1G" and will be converted to - */ - protected string $max_upload_size) + public function __construct( + protected AttachmentPathResolver $pathResolver, + protected HttpClientInterface $httpClient, + protected MimeTypesInterface $mimeTypes, + protected FileTypeFilterTools $filterTools, + protected AttachmentsSettings $settings, + protected readonly SVGSanitizer $SVGSanitizer, + ) { //The mapping used to determine which folder will be used for an attachment type $this->folder_mapping = [ PartAttachment::class => 'part', + PartCustomStateAttachment::class => 'part_custom_state', AttachmentTypeAttachment::class => 'attachment_type', CategoryAttachment::class => 'category', CurrencyAttachment::class => 'currency', @@ -157,6 +165,7 @@ public function generateAttachmentPath(Attachment $attachment, bool $secure_uplo } else { //If not, check for instance of: foreach ($this->folder_mapping as $class => $folder) { + /** @var string $class */ if ($attachment instanceof $class) { $prefix = $folder; break; @@ -207,13 +216,16 @@ public function handleUpload(Attachment $attachment, ?AttachmentUpload $upload): if ($file instanceof UploadedFile) { $this->upload($attachment, $file, $secure_attachment); - } elseif ($upload->downloadUrl && $attachment->isExternal()) { + } elseif ($upload->downloadUrl && $attachment->hasExternal()) { $this->downloadURL($attachment, $secure_attachment); } //Move the attachment files to secure location (and back) if needed $this->moveFile($attachment, $secure_attachment); + //Sanitize the SVG if needed + $this->sanitizeSVGAttachment($attachment); + //Rename blacklisted (unsecure) files to a better extension $this->renameBlacklistedExtensions($attachment); @@ -244,12 +256,12 @@ public function handleUpload(Attachment $attachment, ?AttachmentUpload $upload): protected function renameBlacklistedExtensions(Attachment $attachment): Attachment { //We can not do anything on builtins or external ressources - if ($attachment->isBuiltIn() || $attachment->isExternal()) { + if ($attachment->isBuiltIn() || !$attachment->hasInternal()) { return $attachment; } //Determine the old filepath - $old_path = $this->pathResolver->placeholderToRealPath($attachment->getPath()); + $old_path = $this->pathResolver->placeholderToRealPath($attachment->getInternalPath()); if ($old_path === null || $old_path === '' || !file_exists($old_path)) { return $attachment; } @@ -267,7 +279,7 @@ protected function renameBlacklistedExtensions(Attachment $attachment): Attachme $fs->rename($old_path, $new_path); //Update the attachment - $attachment->setPath($this->pathResolver->realPathToPlaceholder($new_path)); + $attachment->setInternalPath($this->pathResolver->realPathToPlaceholder($new_path)); } @@ -275,17 +287,17 @@ protected function renameBlacklistedExtensions(Attachment $attachment): Attachme } /** - * Move the given attachment to secure location (or back to public folder) if needed. + * Move the internal copy of the given attachment to a secure location (or back to public folder) if needed. * * @param Attachment $attachment the attachment for which the file should be moved * @param bool $secure_location this value determines, if the attachment is moved to the secure or public folder * - * @return Attachment The attachment with the updated filepath + * @return Attachment The attachment with the updated internal filepath */ protected function moveFile(Attachment $attachment, bool $secure_location): Attachment { //We can not do anything on builtins or external ressources - if ($attachment->isBuiltIn() || $attachment->isExternal()) { + if ($attachment->isBuiltIn() || !$attachment->hasInternal()) { return $attachment; } @@ -295,7 +307,7 @@ protected function moveFile(Attachment $attachment, bool $secure_location): Atta } //Determine the old filepath - $old_path = $this->pathResolver->placeholderToRealPath($attachment->getPath()); + $old_path = $this->pathResolver->placeholderToRealPath($attachment->getInternalPath()); if (!file_exists($old_path)) { return $attachment; } @@ -319,7 +331,7 @@ protected function moveFile(Attachment $attachment, bool $secure_location): Atta //Save info to attachment entity $new_path = $this->pathResolver->realPathToPlaceholder($new_path); - $attachment->setPath($new_path); + $attachment->setInternalPath($new_path); return $attachment; } @@ -329,25 +341,44 @@ protected function moveFile(Attachment $attachment, bool $secure_location): Atta * * @param bool $secureAttachment True if the file should be moved to the secure attachment storage * - * @return Attachment The attachment with the new filepath + * @return Attachment The attachment with the downloaded copy */ protected function downloadURL(Attachment $attachment, bool $secureAttachment): Attachment { //Check if we are allowed to download files - if (!$this->allow_attachments_downloads) { + if (!$this->settings->allowDownloads) { throw new RuntimeException('Download of attachments is not allowed!'); } - $url = $attachment->getURL(); + $url = $attachment->getExternalPath(); $fs = new Filesystem(); $attachment_folder = $this->generateAttachmentPath($attachment, $secureAttachment); $tmp_path = $attachment_folder.DIRECTORY_SEPARATOR.$this->generateAttachmentFilename($attachment, 'tmp'); try { - $response = $this->httpClient->request('GET', $url, [ + $opts = [ 'buffer' => false, - ]); + //Use user-agent and other headers to make the server think we are a browser + 'headers' => [ + "sec-ch-ua" => "\"Not(A:Brand\";v=\"99\", \"Google Chrome\";v=\"133\", \"Chromium\";v=\"133\"", + "sec-ch-ua-mobile" => "?0", + "sec-ch-ua-platform" => "\"Windows\"", + "user-agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36", + "sec-fetch-site" => "none", + "sec-fetch-mode" => "navigate", + ], + + ]; + $response = $this->httpClient->request('GET', $url, $opts); + //Digikey wants TLSv1.3, so try again with that if we get a 403 + if ($response->getStatusCode() === 403) { + $opts['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT; + $response = $this->httpClient->request('GET', $url, $opts); + } + # if you have these changes and downloads still fail, check if it's due to an unknown certificate. Curl by + # default uses the systems ca store and that doesn't contain all the intermediate certificates needed to + # verify the leafs if (200 !== $response->getStatusCode()) { throw new AttachmentDownloadException('Status code: '.$response->getStatusCode()); @@ -399,7 +430,7 @@ protected function downloadURL(Attachment $attachment, bool $secureAttachment): //Make our file path relative to %BASE% $new_path = $this->pathResolver->realPathToPlaceholder($new_path); //Save the path to the attachment - $attachment->setPath($new_path); + $attachment->setInternalPath($new_path); } catch (TransportExceptionInterface) { throw new AttachmentDownloadException('Transport error!'); } @@ -427,7 +458,9 @@ protected function upload(Attachment $attachment, UploadedFile $file, bool $secu //Make our file path relative to %BASE% $file_path = $this->pathResolver->realPathToPlaceholder($file_path); //Save the path to the attachment - $attachment->setPath($file_path); + $attachment->setInternalPath($file_path); + //reset any external paths the attachment might have had + $attachment->setExternalPath(null); //And save original filename $attachment->setFilename($file->getClientOriginalName()); @@ -472,9 +505,37 @@ public function getMaximumAllowedUploadSize(): int $this->max_upload_size_bytes = min( $this->parseFileSizeString(ini_get('post_max_size')), $this->parseFileSizeString(ini_get('upload_max_filesize')), - $this->parseFileSizeString($this->max_upload_size), + $this->parseFileSizeString($this->settings->maxFileSize) ); return $this->max_upload_size_bytes; } + + /** + * Sanitizes the given SVG file, if the attachment is an internal SVG file. + * @param Attachment $attachment + * @return Attachment + */ + public function sanitizeSVGAttachment(Attachment $attachment): Attachment + { + //We can not do anything on builtins or external ressources + if ($attachment->isBuiltIn() || !$attachment->hasInternal()) { + return $attachment; + } + + //Resolve the path to the file + $path = $this->pathResolver->placeholderToRealPath($attachment->getInternalPath()); + + //Check if the file exists + if (!file_exists($path)) { + return $attachment; + } + + //Check if the file is an SVG + if ($attachment->getExtension() === "svg") { + $this->SVGSanitizer->sanitizeFile($path); + } + + return $attachment; + } } diff --git a/src/Services/Attachments/AttachmentURLGenerator.php b/src/Services/Attachments/AttachmentURLGenerator.php index d28a8d651..e505408fb 100644 --- a/src/Services/Attachments/AttachmentURLGenerator.php +++ b/src/Services/Attachments/AttachmentURLGenerator.php @@ -92,9 +92,9 @@ public function placeholderPathToAssetPath(string $placeholder_path): ?string * Returns a URL under which the attachment file can be viewed. * @return string|null The URL or null if the attachment file is not existing */ - public function getViewURL(Attachment $attachment): ?string + public function getInternalViewURL(Attachment $attachment): ?string { - $absolute_path = $this->attachmentHelper->toAbsoluteFilePath($attachment); + $absolute_path = $this->attachmentHelper->toAbsoluteInternalFilePath($attachment); if (null === $absolute_path) { return null; } @@ -111,19 +111,23 @@ public function getViewURL(Attachment $attachment): ?string /** * Returns a URL to a thumbnail of the attachment file. - * @return string|null The URL or null if the attachment file is not existing + * For external files the original URL is returned. + * @return string|null The URL or null if the attachment file is not existing or is invalid */ public function getThumbnailURL(Attachment $attachment, string $filter_name = 'thumbnail_sm'): ?string { if (!$attachment->isPicture()) { - throw new InvalidArgumentException('Thumbnail creation only works for picture attachments!'); + return null; } - if ($attachment->isExternal() && ($attachment->getURL() !== null && $attachment->getURL() !== '')) { - return $attachment->getURL(); + if (!$attachment->hasInternal()){ + if($attachment->hasExternal()) { + return $attachment->getExternalPath(); + } + return null; } - $absolute_path = $this->attachmentHelper->toAbsoluteFilePath($attachment); + $absolute_path = $this->attachmentHelper->toAbsoluteInternalFilePath($attachment); if (null === $absolute_path) { return null; } @@ -137,7 +141,7 @@ public function getThumbnailURL(Attachment $attachment, string $filter_name = 't //GD can not work with SVG, so serve it directly... //We can not use getExtension here, because it uses the original filename and not the real extension //Instead we use the logic, which is also used to determine if the attachment is a picture - $extension = pathinfo(parse_url(http://github.com/$attachment-%3EgetPath(), PHP_URL_PATH) ?? '', PATHINFO_EXTENSION); + $extension = pathinfo(parse_url(http://github.com/$attachment-%3EgetInternalPath(), PHP_URL_PATH) ?? '', PATHINFO_EXTENSION); if ('svg' === $extension) { return $this->assets->getUrl($asset_path); } @@ -157,7 +161,7 @@ public function getThumbnailURL(Attachment $attachment, string $filter_name = 't /** * Returns a download link to the file associated with the attachment. */ - public function getDownloadURL(Attachment $attachment): string + public function getInternalDownloadURL(Attachment $attachment): string { //Redirect always to download controller, which sets the correct headers for downloading: return $this->urlGenerator->generate('attachment_download', ['id' => $attachment->getID()]); diff --git a/src/Services/Attachments/PartPreviewGenerator.php b/src/Services/Attachments/PartPreviewGenerator.php index ba6e5db0d..9aedba743 100644 --- a/src/Services/Attachments/PartPreviewGenerator.php +++ b/src/Services/Attachments/PartPreviewGenerator.php @@ -23,6 +23,7 @@ namespace App\Services\Attachments; use App\Entity\Parts\Footprint; +use App\Entity\Parts\PartCustomState; use App\Entity\ProjectSystem\Project; use App\Entity\Parts\Category; use App\Entity\Parts\StorageLocation; diff --git a/src/Services/Attachments/SVGSanitizer.php b/src/Services/Attachments/SVGSanitizer.php new file mode 100644 index 000000000..9ac5956bd --- /dev/null +++ b/src/Services/Attachments/SVGSanitizer.php @@ -0,0 +1,58 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Services\Attachments; + +use Rhukster\DomSanitizer\DOMSanitizer; + +class SVGSanitizer +{ + + /** + * Sanitizes the given SVG string by removing any potentially harmful content (like inline scripts). + * @param string $input + * @return string + */ + public function sanitizeString(string $input): string + { + return (new DOMSanitizer(DOMSanitizer::SVG))->sanitize($input); + } + + /** + * Sanitizes the given SVG file by removing any potentially harmful content (like inline scripts). + * The sanitized content is written back to the file. + * @param string $filepath + */ + public function sanitizeFile(string $filepath): void + { + //Open the file and read the content + $content = file_get_contents($filepath); + if ($content === false) { + throw new \RuntimeException('Could not read file: ' . $filepath); + } + //Sanitize the content + $sanitizedContent = $this->sanitizeString($content); + //Write the sanitized content back to the file + file_put_contents($filepath, $sanitizedContent); + } +} \ No newline at end of file diff --git a/src/Services/EDA/KiCadHelper.php b/src/Services/EDA/KiCadHelper.php index 13de01e82..4b7c5e5a5 100644 --- a/src/Services/EDA/KiCadHelper.php +++ b/src/Services/EDA/KiCadHelper.php @@ -27,7 +27,9 @@ use App\Entity\Parts\Footprint; use App\Entity\Parts\Part; use App\Services\Cache\ElementCacheTagGenerator; +use App\Services\EntityURLGenerator; use App\Services\Trees\NodesListBuilder; +use App\Settings\MiscSettings\KiCadEDASettings; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; @@ -38,16 +40,20 @@ class KiCadHelper { + /** @var int The maximum level of the shown categories. 0 Means only the top level categories are shown. -1 means only a single one containing */ + private readonly int $category_depth; + public function __construct( private readonly NodesListBuilder $nodesListBuilder, private readonly TagAwareCacheInterface $kicadCache, private readonly EntityManagerInterface $em, private readonly ElementCacheTagGenerator $tagGenerator, private readonly UrlGeneratorInterface $urlGenerator, + private readonly EntityURLGenerator $entityURLGenerator, private readonly TranslatorInterface $translator, - /** The maximum level of the shown categories. 0 Means only the top level categories are shown. -1 means only a single one containing */ - private readonly int $category_depth, + KiCadEDASettings $kiCadEDASettings, ) { + $this->category_depth = $kiCadEDASettings->categoryDepth; } /** @@ -64,6 +70,10 @@ public function getCategories(): array $secure_class_name = $this->tagGenerator->getElementTypeCacheTag(Category::class); $item->tag($secure_class_name); + //Invalidate the cache on part changes (as the visibility depends on parts, and the parts can change) + $secure_class_name = $this->tagGenerator->getElementTypeCacheTag(Part::class); + $item->tag($secure_class_name); + //If the category depth is smaller than 0, create only one dummy category if ($this->category_depth < 0) { return [ @@ -108,6 +118,8 @@ public function getCategories(): array $result[] = [ 'id' => (string)$category->getId(), 'name' => $category->getFullPath('/'), + //Show the category link as the category description, this also fixes an segfault in KiCad see issue #878 + 'description' => $this->entityURLGenerator->listPartsURL($category), ]; } @@ -221,6 +233,10 @@ public function getKiCADPart(Part $part): array } $result["fields"]["Part-DB Unit"] = $this->createField($unit); } + if ($part->getPartCustomState() !== null) { + $customState = $part->getPartCustomState()->getName(); + $result["fields"]["Part-DB Custom state"] = $this->createField($customState); + } if ($part->getMass()) { $result["fields"]["Mass"] = $this->createField($part->getMass() . ' g'); } @@ -229,6 +245,49 @@ public function getKiCADPart(Part $part): array $result["fields"]["Part-DB IPN"] = $this->createField($part->getIpn()); } + // Add supplier information from orderdetails (include obsolete orderdetails) + if ($part->getOrderdetails(false)->count() > 0) { + $supplierCounts = []; + + foreach ($part->getOrderdetails(false) as $orderdetail) { + if ($orderdetail->getSupplier() !== null && $orderdetail->getSupplierPartNr() !== '') { + $supplierName = $orderdetail->getSupplier()->getName(); + + $supplierName .= " SPN"; // Append "SPN" to the supplier name to indicate Supplier Part Number + + if (!isset($supplierCounts[$supplierName])) { + $supplierCounts[$supplierName] = 0; + } + $supplierCounts[$supplierName]++; + + // Create field name with sequential number if more than one from same supplier (e.g. "Mouser", "Mouser 2", etc.) + $fieldName = $supplierCounts[$supplierName] > 1 + ? $supplierName . ' ' . $supplierCounts[$supplierName] + : $supplierName; + + $result["fields"][$fieldName] = $this->createField($orderdetail->getSupplierPartNr()); + } + } + } + + //Add fields for KiCost: + if ($part->getManufacturer() !== null) { + $result["fields"]["manf"] = $this->createField($part->getManufacturer()->getName()); + } + if ($part->getManufacturerProductNumber() !== "") { + $result['fields']['manf#'] = $this->createField($part->getManufacturerProductNumber()); + } + + //For each supplier, add a field with the supplier name and the supplier part number for KiCost + if ($part->getOrderdetails(false)->count() > 0) { + foreach ($part->getOrderdetails(false) as $orderdetail) { + if ($orderdetail->getSupplier() !== null && $orderdetail->getSupplierPartNr() !== '') { + $fieldName = mb_strtolower($orderdetail->getSupplier()->getName()) . '#'; + + $result["fields"][$fieldName] = $this->createField($orderdetail->getSupplierPartNr()); + } + } + } return $result; } diff --git a/src/Services/ElementTypeNameGenerator.php b/src/Services/ElementTypeNameGenerator.php index 142471457..deb4cf30c 100644 --- a/src/Services/ElementTypeNameGenerator.php +++ b/src/Services/ElementTypeNameGenerator.php @@ -22,13 +22,13 @@ namespace App\Services; -use App\Entity\Attachments\AttachmentContainingDBElement; use App\Entity\Attachments\Attachment; +use App\Entity\Attachments\AttachmentContainingDBElement; use App\Entity\Attachments\AttachmentType; use App\Entity\Base\AbstractDBElement; use App\Entity\Contracts\NamedElementInterface; -use App\Entity\Parts\PartAssociation; -use App\Entity\ProjectSystem\Project; +use App\Entity\InfoProviderSystem\BulkInfoProviderImportJob; +use App\Entity\InfoProviderSystem\BulkInfoProviderImportJobPart; use App\Entity\LabelSystem\LabelProfile; use App\Entity\Parameters\AbstractParameter; use App\Entity\Parts\Category; @@ -36,12 +36,15 @@ use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; +use App\Entity\Parts\PartAssociation; +use App\Entity\Parts\PartCustomState; use App\Entity\Parts\PartLot; use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; use App\Entity\PriceInformations\Orderdetail; use App\Entity\PriceInformations\Pricedetail; +use App\Entity\ProjectSystem\Project; use App\Entity\ProjectSystem\ProjectBOMEntry; use App\Entity\UserSystem\Group; use App\Entity\UserSystem\User; @@ -79,6 +82,9 @@ public function __construct(protected TranslatorInterface $translator, private r AbstractParameter::class => $this->translator->trans('parameter.label'), LabelProfile::class => $this->translator->trans('label_profile.label'), PartAssociation::class => $this->translator->trans('part_association.label'), + BulkInfoProviderImportJob::class => $this->translator->trans('bulk_info_provider_import_job.label'), + BulkInfoProviderImportJobPart::class => $this->translator->trans('bulk_info_provider_import_job_part.label'), + PartCustomState::class => $this->translator->trans('part_custom_state.label'), ]; } @@ -130,10 +136,10 @@ public function getTypeNameCombination(NamedElementInterface $entity, bool $use_ { $type = $this->getLocalizedTypeLabel($entity); if ($use_html) { - return ''.$type.': '.htmlspecialchars($entity->getName()); + return '' . $type . ': ' . htmlspecialchars($entity->getName()); } - return $type.': '.$entity->getName(); + return $type . ': ' . $entity->getName(); } diff --git a/src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php b/src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php index a5c9a5fa6..64c952a97 100644 --- a/src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php +++ b/src/Services/EntityMergers/Mergers/EntityMergerHelperTrait.php @@ -247,7 +247,8 @@ protected function mergeAttachments(AttachmentContainingDBElement $target, Attac { return $this->mergeCollections($target, $other, 'attachments', fn(Attachment $t, Attachment $o): bool => $t->getName() === $o->getName() && $t->getAttachmentType() === $o->getAttachmentType() - && $t->getPath() === $o->getPath()); + && $t->getExternalPath() === $o->getExternalPath() + && $t->getInternalPath() === $o->getInternalPath()); } /** diff --git a/src/Services/EntityMergers/Mergers/PartMerger.php b/src/Services/EntityMergers/Mergers/PartMerger.php index 4ce779e88..d1f5c1374 100644 --- a/src/Services/EntityMergers/Mergers/PartMerger.php +++ b/src/Services/EntityMergers/Mergers/PartMerger.php @@ -65,6 +65,7 @@ public function merge(object $target, object $other, array $context = []): Part $this->useOtherValueIfNotNull($target, $other, 'footprint'); $this->useOtherValueIfNotNull($target, $other, 'category'); $this->useOtherValueIfNotNull($target, $other, 'partUnit'); + $this->useOtherValueIfNotNull($target, $other, 'partCustomState'); //We assume that the higher value is the correct one for minimum instock $this->useLargerValue($target, $other, 'minamount'); @@ -100,7 +101,8 @@ public function merge(object $target, object $other, array $context = []): Part return $target; } - private function comparePartAssociations(PartAssociation $t, PartAssociation $o): bool { + private function comparePartAssociations(PartAssociation $t, PartAssociation $o): bool + { //We compare the translation keys, as it contains info about the type and other type info return $t->getOther() === $o->getOther() && $t->getTypeTranslationKey() === $o->getTypeTranslationKey(); @@ -141,40 +143,39 @@ private function mergeCollectionFields(Part $target, Part $other, array $context $owner->addAssociatedPartsAsOwner($clone); } + // Merge orderdetails, considering same supplier+part number as duplicates $this->mergeCollections($target, $other, 'orderdetails', function (Orderdetail $t, Orderdetail $o) { - //First check that the orderdetails infos are equal - $tmp = $t->getSupplier() === $o->getSupplier() - && $t->getSupplierPartNr() === $o->getSupplierPartNr() - && $t->getSupplierProductUrl(false) === $o->getSupplierProductUrl(false); - - if (!$tmp) { - return false; - } - - //Check if the pricedetails are equal - $t_pricedetails = $t->getPricedetails(); - $o_pricedetails = $o->getPricedetails(); - //Ensure that both pricedetails have the same length - if (count($t_pricedetails) !== count($o_pricedetails)) { - return false; - } - - //Check if all pricedetails are equal - for ($n=0, $nMax = count($t_pricedetails); $n< $nMax; $n++) { - $t_price = $t_pricedetails->get($n); - $o_price = $o_pricedetails->get($n); - - if (!$t_price->getPrice()->isEqualTo($o_price->getPrice()) - || $t_price->getCurrency() !== $o_price->getCurrency() - || $t_price->getPriceRelatedQuantity() !== $o_price->getPriceRelatedQuantity() - || $t_price->getMinDiscountQuantity() !== $o_price->getMinDiscountQuantity() - ) { - return false; + // If supplier and part number match, merge the orderdetails + if ($t->getSupplier() === $o->getSupplier() && $t->getSupplierPartNr() === $o->getSupplierPartNr()) { + // Update URL if target doesn't have one + if (empty($t->getSupplierProductUrl(false)) && !empty($o->getSupplierProductUrl(false))) { + $t->setSupplierProductUrl($o->getSupplierProductUrl(false)); } + // Merge price details: add new ones, update empty ones, keep existing non-empty ones + foreach ($o->getPricedetails() as $otherPrice) { + $found = false; + foreach ($t->getPricedetails() as $targetPrice) { + if ($targetPrice->getMinDiscountQuantity() === $otherPrice->getMinDiscountQuantity() + && $targetPrice->getCurrency() === $otherPrice->getCurrency()) { + // Only update price if the existing one is zero/empty (most logical) + if ($targetPrice->getPrice()->isZero()) { + $targetPrice->setPrice($otherPrice->getPrice()); + $targetPrice->setPriceRelatedQuantity($otherPrice->getPriceRelatedQuantity()); + } + $found = true; + break; + } + } + // Add completely new price tiers + if (!$found) { + $clonedPrice = clone $otherPrice; + $clonedPrice->setOrderdetail($t); + $t->addPricedetail($clonedPrice); + } + } + return true; // Consider them equal so the other one gets skipped } - - //If all pricedetails are equal, the orderdetails are equal - return true; + return false; // Different supplier/part number, add as new }); //The pricedetails are not correctly assigned to the new orderdetails, so fix that foreach ($target->getOrderdetails() as $orderdetail) { diff --git a/src/Services/EntityURLGenerator.php b/src/Services/EntityURLGenerator.php index 5718daec1..91e271cc0 100644 --- a/src/Services/EntityURLGenerator.php +++ b/src/Services/EntityURLGenerator.php @@ -27,6 +27,7 @@ use App\Entity\Attachments\PartAttachment; use App\Entity\Base\AbstractDBElement; use App\Entity\Parameters\PartParameter; +use App\Entity\Parts\PartCustomState; use App\Entity\ProjectSystem\Project; use App\Entity\LabelSystem\LabelProfile; use App\Entity\Parts\Category; @@ -107,6 +108,7 @@ public function timeTravelURL(AbstractDBElement $entity, \DateTimeInterface $dat MeasurementUnit::class => 'measurement_unit_edit', Group::class => 'group_edit', LabelProfile::class => 'label_profile_edit', + PartCustomState::class => 'part_custom_state_edit', ]; try { @@ -156,25 +158,34 @@ public function timeTravelURL(AbstractDBElement $entity, \DateTimeInterface $dat public function viewURL(Attachment $entity): string { - if ($entity->isExternal()) { //For external attachments, return the link to external path - return $entity->getURL() ?? throw new \RuntimeException('External attachment has no URL!'); + //If the underlying file path is invalid, null gets returned, which is not allowed here. + //We still have the chance to use an external path, if it is set. + if ($entity->hasInternal() && ($url = $this->attachmentURLGenerator->getInternalViewURL($entity)) !== null) { + return $url; } - //return $this->urlGenerator->generate('attachment_view', ['id' => $entity->getID()]); - return $this->attachmentURLGenerator->getViewURL($entity) ?? ''; + + if($entity->hasExternal()) { + return $entity->getExternalPath(); + } + + throw new \RuntimeException('Attachment has no internal nor external path!'); } public function downloadURL($entity): string { - if ($entity instanceof Attachment) { - if ($entity->isExternal()) { //For external attachments, return the link to external path - return $entity->getURL() ?? throw new \RuntimeException('External attachment has no URL!'); - } + if (!($entity instanceof Attachment)) { + throw new EntityNotSupportedException(sprintf('The given entity is not supported yet! Passed class type: %s', $entity::class)); + } - return $this->attachmentURLGenerator->getDownloadURL($entity); + if ($entity->hasInternal()) { + return $this->attachmentURLGenerator->getInternalDownloadURL($entity); } - //Otherwise throw an error - throw new EntityNotSupportedException(sprintf('The given entity is not supported yet! Passed class type: %s', $entity::class)); + if($entity->hasExternal()) { + return $entity->getExternalPath(); + } + + throw new \RuntimeException('Attachment has not internal or external path!'); } /** @@ -204,6 +215,7 @@ public function infoURL(AbstractDBElement $entity): string MeasurementUnit::class => 'measurement_unit_edit', Group::class => 'group_edit', LabelProfile::class => 'label_profile_edit', + PartCustomState::class => 'part_custom_state_edit', ]; return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]); @@ -234,6 +246,7 @@ public function editURL(AbstractDBElement $entity): string MeasurementUnit::class => 'measurement_unit_edit', Group::class => 'group_edit', LabelProfile::class => 'label_profile_edit', + PartCustomState::class => 'part_custom_state_edit', ]; return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]); @@ -265,6 +278,7 @@ public function createURL(AbstractDBElement|string $entity): string MeasurementUnit::class => 'measurement_unit_new', Group::class => 'group_new', LabelProfile::class => 'label_profile_new', + PartCustomState::class => 'part_custom_state_new', ]; return $this->urlGenerator->generate($this->mapToController($map, $entity)); @@ -296,6 +310,7 @@ public function cloneURL(AbstractDBElement $entity): string MeasurementUnit::class => 'measurement_unit_clone', Group::class => 'group_clone', LabelProfile::class => 'label_profile_clone', + PartCustomState::class => 'part_custom_state_clone', ]; return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]); @@ -341,6 +356,7 @@ public function deleteURL(AbstractDBElement $entity): string MeasurementUnit::class => 'measurement_unit_delete', Group::class => 'group_delete', LabelProfile::class => 'label_profile_delete', + PartCustomState::class => 'part_custom_state_delete', ]; return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]); diff --git a/src/Services/Formatters/MoneyFormatter.php b/src/Services/Formatters/MoneyFormatter.php index 44a49cb55..505752c36 100644 --- a/src/Services/Formatters/MoneyFormatter.php +++ b/src/Services/Formatters/MoneyFormatter.php @@ -23,6 +23,7 @@ namespace App\Services\Formatters; use App\Entity\PriceInformations\Currency; +use App\Settings\SystemSettings\LocalizationSettings; use Locale; use NumberFormatter; @@ -30,7 +31,7 @@ class MoneyFormatter { protected string $locale; - public function __construct(protected string $base_currency) + public function __construct(private readonly LocalizationSettings $localizationSettings) { $this->locale = Locale::getDefault(); } @@ -45,7 +46,7 @@ public function __construct(protected string $base_currency) */ public function format(string|float $value, ?Currency $currency = null, int $decimals = 5, bool $show_all_digits = false): string { - $iso_code = $this->base_currency; + $iso_code = $this->localizationSettings->baseCurrency; if ($currency instanceof Currency && ($currency->getIsoCode() !== '')) { $iso_code = $currency->getIsoCode(); } diff --git a/src/Services/Formatters/SIFormatter.php b/src/Services/Formatters/SIFormatter.php index a6325987c..b83501fae 100644 --- a/src/Services/Formatters/SIFormatter.php +++ b/src/Services/Formatters/SIFormatter.php @@ -38,6 +38,11 @@ class SIFormatter */ public function getMagnitude(float $value): int { + //Check for zero, as log10(0) is undefined/gives -infinity, which leads to casting issues in PHP8.5+ + if (0.0 === $value) { + return 0; + } + return (int) floor(log10(abs($value))); } diff --git a/src/Services/ImportExportSystem/BOMImporter.php b/src/Services/ImportExportSystem/BOMImporter.php index d48764451..862fa463f 100644 --- a/src/Services/ImportExportSystem/BOMImporter.php +++ b/src/Services/ImportExportSystem/BOMImporter.php @@ -22,10 +22,13 @@ */ namespace App\Services\ImportExportSystem; +use App\Entity\Parts\Part; use App\Entity\ProjectSystem\Project; use App\Entity\ProjectSystem\ProjectBOMEntry; +use Doctrine\ORM\EntityManagerInterface; use InvalidArgumentException; use League\Csv\Reader; +use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -44,14 +47,25 @@ class BOMImporter 5 => 'Supplier and ref', ]; - public function __construct() - { + public function __construct( + private readonly EntityManagerInterface $entityManager, + private readonly LoggerInterface $logger, + private readonly BOMValidationService $validationService + ) { } protected function configureOptions(OptionsResolver $resolver): OptionsResolver { $resolver->setRequired('type'); - $resolver->setAllowedValues('type', ['kicad_pcbnew']); + $resolver->setAllowedValues('type', ['kicad_pcbnew', 'kicad_schematic']); + + // For flexible schematic import with field mapping + $resolver->setDefined(['field_mapping', 'field_priorities', 'delimiter']); + $resolver->setDefault('delimiter', ','); + $resolver->setDefault('field_priorities', []); + $resolver->setAllowedTypes('field_mapping', 'array'); + $resolver->setAllowedTypes('field_priorities', 'array'); + $resolver->setAllowedTypes('delimiter', 'string'); return $resolver; } @@ -82,6 +96,23 @@ public function fileToBOMEntries(File $file, array $options): array return $this->stringToBOMEntries($file->getContent(), $options); } + /** + * Validate BOM data before importing + * @return array Validation result with errors, warnings, and info + */ + public function validateBOMData(string $data, array $options): array + { + $resolver = new OptionsResolver(); + $resolver = $this->configureOptions($resolver); + $options = $resolver->resolve($options); + + return match ($options['type']) { + 'kicad_pcbnew' => $this->validateKiCADPCB($data), + 'kicad_schematic' => $this->validateKiCADSchematicData($data, $options), + default => throw new InvalidArgumentException('Invalid import type!'), + }; + } + /** * Import string data into an array of BOM entries, which are not yet assigned to a project. * @param string $data The data to import @@ -95,12 +126,13 @@ public function stringToBOMEntries(string $data, array $options): array $options = $resolver->resolve($options); return match ($options['type']) { - 'kicad_pcbnew' => $this->parseKiCADPCB($data, $options), + 'kicad_pcbnew' => $this->parseKiCADPCB($data), + 'kicad_schematic' => $this->parseKiCADSchematic($data, $options), default => throw new InvalidArgumentException('Invalid import type!'), }; } - private function parseKiCADPCB(string $data, array $options = []): array + private function parseKiCADPCB(string $data): array { $csv = Reader::createFromString($data); $csv->setDelimiter(';'); @@ -113,17 +145,17 @@ private function parseKiCADPCB(string $data, array $options = []): array $entry = $this->normalizeColumnNames($entry); //Ensure that the entry has all required fields - if (!isset ($entry['Designator'])) { - throw new \UnexpectedValueException('Designator missing at line '.($offset + 1).'!'); + if (!isset($entry['Designator'])) { + throw new \UnexpectedValueException('Designator missing at line ' . ($offset + 1) . '!'); } - if (!isset ($entry['Package'])) { - throw new \UnexpectedValueException('Package missing at line '.($offset + 1).'!'); + if (!isset($entry['Package'])) { + throw new \UnexpectedValueException('Package missing at line ' . ($offset + 1) . '!'); } - if (!isset ($entry['Designation'])) { - throw new \UnexpectedValueException('Designation missing at line '.($offset + 1).'!'); + if (!isset($entry['Designation'])) { + throw new \UnexpectedValueException('Designation missing at line ' . ($offset + 1) . '!'); } - if (!isset ($entry['Quantity'])) { - throw new \UnexpectedValueException('Quantity missing at line '.($offset + 1).'!'); + if (!isset($entry['Quantity'])) { + throw new \UnexpectedValueException('Quantity missing at line ' . ($offset + 1) . '!'); } $bom_entry = new ProjectBOMEntry(); @@ -138,6 +170,63 @@ private function parseKiCADPCB(string $data, array $options = []): array return $bom_entries; } + /** + * Validate KiCad PCB data + */ + private function validateKiCADPCB(string $data): array + { + $csv = Reader::createFromString($data); + $csv->setDelimiter(';'); + $csv->setHeaderOffset(0); + + $mapped_entries = []; + + foreach ($csv->getRecords() as $offset => $entry) { + // Translate the german field names to english + $entry = $this->normalizeColumnNames($entry); + $mapped_entries[] = $entry; + } + + return $this->validationService->validateBOMEntries($mapped_entries); + } + + /** + * Validate KiCad schematic data + */ + private function validateKiCADSchematicData(string $data, array $options): array + { + $delimiter = $options['delimiter'] ?? ','; + $field_mapping = $options['field_mapping'] ?? []; + $field_priorities = $options['field_priorities'] ?? []; + + // Handle potential BOM (Byte Order Mark) at the beginning + $data = preg_replace('/^\xEF\xBB\xBF/', '', $data); + + $csv = Reader::createFromString($data); + $csv->setDelimiter($delimiter); + $csv->setHeaderOffset(0); + + // Handle quoted fields properly + $csv->setEscape('\\'); + $csv->setEnclosure('"'); + + $mapped_entries = []; + + foreach ($csv->getRecords() as $offset => $entry) { + // Apply field mapping to translate column names + $mapped_entry = $this->applyFieldMapping($entry, $field_mapping, $field_priorities); + + // Extract footprint package name if it contains library prefix + if (isset($mapped_entry['Package']) && str_contains($mapped_entry['Package'], ':')) { + $mapped_entry['Package'] = explode(':', $mapped_entry['Package'], 2)[1]; + } + + $mapped_entries[] = $mapped_entry; + } + + return $this->validationService->validateBOMEntries($mapped_entries, $options); + } + /** * This function uses the order of the fields in the CSV files to make them locale independent. * @param array $entry @@ -160,4 +249,482 @@ private function normalizeColumnNames(array $entry): array return $out; } + + /** + * Parse KiCad schematic BOM with flexible field mapping + */ + private function parseKiCADSchematic(string $data, array $options = []): array + { + $delimiter = $options['delimiter'] ?? ','; + $field_mapping = $options['field_mapping'] ?? []; + $field_priorities = $options['field_priorities'] ?? []; + + // Handle potential BOM (Byte Order Mark) at the beginning + $data = preg_replace('/^\xEF\xBB\xBF/', '', $data); + + $csv = Reader::createFromString($data); + $csv->setDelimiter($delimiter); + $csv->setHeaderOffset(0); + + // Handle quoted fields properly + $csv->setEscape('\\'); + $csv->setEnclosure('"'); + + $bom_entries = []; + $entries_by_key = []; // Track entries by name+part combination + $mapped_entries = []; // Collect all mapped entries for validation + + foreach ($csv->getRecords() as $offset => $entry) { + // Apply field mapping to translate column names + $mapped_entry = $this->applyFieldMapping($entry, $field_mapping, $field_priorities); + + // Extract footprint package name if it contains library prefix + if (isset($mapped_entry['Package']) && str_contains($mapped_entry['Package'], ':')) { + $mapped_entry['Package'] = explode(':', $mapped_entry['Package'], 2)[1]; + } + + $mapped_entries[] = $mapped_entry; + } + + // Validate all entries before processing + $validation_result = $this->validationService->validateBOMEntries($mapped_entries, $options); + + // Log validation results + $this->logger->info('BOM import validation completed', [ + 'total_entries' => $validation_result['total_entries'], + 'valid_entries' => $validation_result['valid_entries'], + 'invalid_entries' => $validation_result['invalid_entries'], + 'error_count' => count($validation_result['errors']), + 'warning_count' => count($validation_result['warnings']), + ]); + + // If there are validation errors, throw an exception with detailed messages + if (!empty($validation_result['errors'])) { + $error_message = $this->validationService->getErrorMessage($validation_result); + throw new \UnexpectedValueException("BOM import validation failed:\n" . $error_message); + } + + // Process validated entries + foreach ($mapped_entries as $offset => $mapped_entry) { + + // Set name - prefer MPN, fall back to Value, then default format + $mpn = trim($mapped_entry['MPN'] ?? ''); + $designation = trim($mapped_entry['Designation'] ?? ''); + $value = trim($mapped_entry['Value'] ?? ''); + + // Use the first non-empty value, or 'Unknown Component' if all are empty + $name = ''; + if (!empty($mpn)) { + $name = $mpn; + } elseif (!empty($designation)) { + $name = $designation; + } elseif (!empty($value)) { + $name = $value; + } else { + $name = 'Unknown Component'; + } + + if (isset($mapped_entry['Package']) && !empty(trim($mapped_entry['Package']))) { + $name .= ' (' . trim($mapped_entry['Package']) . ')'; + } + + // Set mountnames and quantity + // The Designator field contains comma-separated mount names for all instances + $designator = trim($mapped_entry['Designator']); + $quantity = (float) $mapped_entry['Quantity']; + + // Get mountnames array (validation already ensured they match quantity) + $mountnames_array = array_map('trim', explode(',', $designator)); + + // Try to link existing Part-DB part if ID is provided + $part = null; + if (isset($mapped_entry['Part-DB ID']) && !empty($mapped_entry['Part-DB ID'])) { + $partDbId = (int) $mapped_entry['Part-DB ID']; + $existingPart = $this->entityManager->getRepository(Part::class)->find($partDbId); + + if ($existingPart) { + $part = $existingPart; + // Update name with actual part name + $name = $existingPart->getName(); + } + } + + // Create unique key for this entry (name + part ID) + $entry_key = $name . '|' . ($part ? $part->getID() : 'null'); + + // Check if we already have an entry with the same name and part + if (isset($entries_by_key[$entry_key])) { + // Merge with existing entry + $existing_entry = $entries_by_key[$entry_key]; + + // Combine mountnames + $existing_mountnames = $existing_entry->getMountnames(); + $combined_mountnames = $existing_mountnames . ',' . $designator; + $existing_entry->setMountnames($combined_mountnames); + + // Add quantities + $existing_quantity = $existing_entry->getQuantity(); + $existing_entry->setQuantity($existing_quantity + $quantity); + + $this->logger->info('Merged duplicate BOM entry', [ + 'name' => $name, + 'part_id' => $part ? $part->getID() : null, + 'original_quantity' => $existing_quantity, + 'added_quantity' => $quantity, + 'new_quantity' => $existing_quantity + $quantity, + 'original_mountnames' => $existing_mountnames, + 'added_mountnames' => $designator, + ]); + + continue; // Skip creating new entry + } + + // Create new BOM entry + $bom_entry = new ProjectBOMEntry(); + $bom_entry->setName($name); + $bom_entry->setMountnames($designator); + $bom_entry->setQuantity($quantity); + + if ($part) { + $bom_entry->setPart($part); + } + + // Set comment with additional info + $comment_parts = []; + if (isset($mapped_entry['Value']) && $mapped_entry['Value'] !== ($mapped_entry['MPN'] ?? '')) { + $comment_parts[] = 'Value: ' . $mapped_entry['Value']; + } + if (isset($mapped_entry['MPN'])) { + $comment_parts[] = 'MPN: ' . $mapped_entry['MPN']; + } + if (isset($mapped_entry['Manufacturer'])) { + $comment_parts[] = 'Manf: ' . $mapped_entry['Manufacturer']; + } + if (isset($mapped_entry['LCSC'])) { + $comment_parts[] = 'LCSC: ' . $mapped_entry['LCSC']; + } + if (isset($mapped_entry['Supplier and ref'])) { + $comment_parts[] = $mapped_entry['Supplier and ref']; + } + + if ($part) { + $comment_parts[] = "Part-DB ID: " . $part->getID(); + } elseif (isset($mapped_entry['Part-DB ID']) && !empty($mapped_entry['Part-DB ID'])) { + $comment_parts[] = "Part-DB ID: " . $mapped_entry['Part-DB ID'] . " (NOT FOUND)"; + } + + $bom_entry->setComment(implode(', ', $comment_parts)); + + $bom_entries[] = $bom_entry; + $entries_by_key[$entry_key] = $bom_entry; + } + + return $bom_entries; + } + + /** + * Get all available field mapping targets with descriptions + */ + public function getAvailableFieldTargets(): array + { + $targets = [ + 'Designator' => [ + 'label' => 'Designator', + 'description' => 'Component reference designators (e.g., R1, C2, U3)', + 'required' => true, + 'multiple' => false, + ], + 'Quantity' => [ + 'label' => 'Quantity', + 'description' => 'Number of components', + 'required' => true, + 'multiple' => false, + ], + 'Designation' => [ + 'label' => 'Designation', + 'description' => 'Component designation/part number', + 'required' => false, + 'multiple' => true, + ], + 'Value' => [ + 'label' => 'Value', + 'description' => 'Component value (e.g., 10k, 100nF)', + 'required' => false, + 'multiple' => true, + ], + 'Package' => [ + 'label' => 'Package', + 'description' => 'Component package/footprint', + 'required' => false, + 'multiple' => true, + ], + 'MPN' => [ + 'label' => 'MPN', + 'description' => 'Manufacturer Part Number', + 'required' => false, + 'multiple' => true, + ], + 'Manufacturer' => [ + 'label' => 'Manufacturer', + 'description' => 'Component manufacturer name', + 'required' => false, + 'multiple' => true, + ], + 'Part-DB ID' => [ + 'label' => 'Part-DB ID', + 'description' => 'Existing Part-DB part ID for linking', + 'required' => false, + 'multiple' => false, + ], + 'Comment' => [ + 'label' => 'Comment', + 'description' => 'Additional component information', + 'required' => false, + 'multiple' => true, + ], + ]; + + // Add dynamic supplier fields based on available suppliers in the database + $suppliers = $this->entityManager->getRepository(\App\Entity\Parts\Supplier::class)->findAll(); + foreach ($suppliers as $supplier) { + $supplierName = $supplier->getName(); + $targets[$supplierName . ' SPN'] = [ + 'label' => $supplierName . ' SPN', + 'description' => "Supplier part number for {$supplierName}", + 'required' => false, + 'multiple' => true, + 'supplier_id' => $supplier->getID(), + ]; + } + + return $targets; + } + + /** + * Get suggested field mappings based on common field names + */ + public function getSuggestedFieldMapping(array $detected_fields): array + { + $suggestions = []; + + $field_patterns = [ + 'Part-DB ID' => ['part-db id', 'partdb_id', 'part_db_id', 'db_id', 'partdb'], + 'Designator' => ['reference', 'ref', 'designator', 'component', 'comp'], + 'Quantity' => ['qty', 'quantity', 'count', 'number', 'amount'], + 'Value' => ['value', 'val', 'component_value'], + 'Designation' => ['designation', 'part_number', 'partnumber', 'part'], + 'Package' => ['footprint', 'package', 'housing', 'fp'], + 'MPN' => ['mpn', 'part_number', 'partnumber', 'manf#', 'mfr_part_number', 'manufacturer_part'], + 'Manufacturer' => ['manufacturer', 'manf', 'mfr', 'brand', 'vendor'], + 'Comment' => ['comment', 'comments', 'note', 'notes', 'description'], + ]; + + // Add supplier-specific patterns + $suppliers = $this->entityManager->getRepository(\App\Entity\Parts\Supplier::class)->findAll(); + foreach ($suppliers as $supplier) { + $supplierName = $supplier->getName(); + $supplierLower = strtolower($supplierName); + + // Create patterns for each supplier + $field_patterns[$supplierName . ' SPN'] = [ + $supplierLower, + $supplierLower . '#', + $supplierLower . '_part', + $supplierLower . '_number', + $supplierLower . 'pn', + $supplierLower . '_spn', + $supplierLower . ' spn', + // Common abbreviations + $supplierLower === 'mouser' ? 'mouser' : null, + $supplierLower === 'digikey' ? 'dk' : null, + $supplierLower === 'farnell' ? 'farnell' : null, + $supplierLower === 'rs' ? 'rs' : null, + $supplierLower === 'lcsc' ? 'lcsc' : null, + ]; + + // Remove null values + $field_patterns[$supplierName . ' SPN'] = array_filter($field_patterns[$supplierName . ' SPN'], fn($value) => $value !== null); + } + + foreach ($detected_fields as $field) { + $field_lower = strtolower(trim($field)); + + foreach ($field_patterns as $target => $patterns) { + foreach ($patterns as $pattern) { + if (str_contains($field_lower, $pattern)) { + $suggestions[$field] = $target; + break 2; // Break both loops + } + } + } + } + + return $suggestions; + } + + /** + * Validate field mapping configuration + */ + public function validateFieldMapping(array $field_mapping, array $detected_fields): array + { + $errors = []; + $warnings = []; + $available_targets = $this->getAvailableFieldTargets(); + + // Check for required fields + $mapped_targets = array_values($field_mapping); + $required_fields = ['Designator', 'Quantity']; + + foreach ($required_fields as $required) { + if (!in_array($required, $mapped_targets, true)) { + $errors[] = "Required field '{$required}' is not mapped from any CSV column."; + } + } + + // Check for invalid target fields + foreach ($field_mapping as $csv_field => $target) { + if (!empty($target) && !isset($available_targets[$target])) { + $errors[] = "Invalid target field '{$target}' for CSV field '{$csv_field}'."; + } + } + + // Check for unmapped fields (warnings) + $unmapped_fields = array_diff($detected_fields, array_keys($field_mapping)); + if (!empty($unmapped_fields)) { + $warnings[] = "The following CSV fields are not mapped: " . implode(', ', $unmapped_fields); + } + + return [ + 'errors' => $errors, + 'warnings' => $warnings, + 'is_valid' => empty($errors), + ]; + } + + /** + * Apply field mapping with support for multiple fields and priority + */ + private function applyFieldMapping(array $entry, array $field_mapping, array $field_priorities = []): array + { + $mapped = []; + $field_groups = []; + + // Group fields by target with priority information + foreach ($field_mapping as $csv_field => $target) { + if (!empty($target)) { + if (!isset($field_groups[$target])) { + $field_groups[$target] = []; + } + $priority = $field_priorities[$csv_field] ?? 10; + $field_groups[$target][] = [ + 'field' => $csv_field, + 'priority' => $priority, + 'value' => $entry[$csv_field] ?? '' + ]; + } + } + + // Process each target field + foreach ($field_groups as $target => $field_data) { + // Sort by priority (lower number = higher priority) + usort($field_data, function ($a, $b) { + return $a['priority'] <=> $b['priority']; + }); + + $values = []; + $non_empty_values = []; + + // Collect all non-empty values for this target + foreach ($field_data as $data) { + $value = trim($data['value']); + if (!empty($value)) { + $non_empty_values[] = $value; + } + $values[] = $value; + } + + // Use the first non-empty value (highest priority) + if (!empty($non_empty_values)) { + $mapped[$target] = $non_empty_values[0]; + + // If multiple non-empty values exist, add alternatives to comment + if (count($non_empty_values) > 1) { + $mapped[$target . '_alternatives'] = array_slice($non_empty_values, 1); + } + } + } + + return $mapped; + } + + /** + * Detect available fields in CSV data for field mapping UI + */ + public function detectFields(string $data, ?string $delimiter = null): array + { + if ($delimiter === null) { + // Detect delimiter by counting occurrences in the first row (header) + $delimiters = [',', ';', "\t"]; + $lines = explode("\n", $data, 2); + $header_line = $lines[0] ?? ''; + $delimiter_counts = []; + foreach ($delimiters as $delim) { + $delimiter_counts[$delim] = substr_count($header_line, $delim); + } + // Choose the delimiter with the highest count, default to comma if all are zero + $max_count = max($delimiter_counts); + $delimiter = array_search($max_count, $delimiter_counts, true); + if ($max_count === 0 || $delimiter === false) { + $delimiter = ','; + } + } + // Handle potential BOM (Byte Order Mark) at the beginning + $data = preg_replace('/^\xEF\xBB\xBF/', '', $data); + + // Get first line only for header detection + $lines = explode("\n", $data); + $header_line = trim($lines[0] ?? ''); + + + // Simple manual parsing for header detection + // This handles quoted CSV fields better than the library for detection + $fields = []; + $current_field = ''; + $in_quotes = false; + $quote_char = '"'; + + for ($i = 0; $i < strlen($header_line); $i++) { + $char = $header_line[$i]; + + if ($char === $quote_char && !$in_quotes) { + $in_quotes = true; + } elseif ($char === $quote_char && $in_quotes) { + // Check for escaped quote (double quote) + if ($i + 1 < strlen($header_line) && $header_line[$i + 1] === $quote_char) { + $current_field .= $quote_char; + $i++; // Skip next quote + } else { + $in_quotes = false; + } + } elseif ($char === $delimiter && !$in_quotes) { + $fields[] = trim($current_field); + $current_field = ''; + } else { + $current_field .= $char; + } + } + + // Add the last field + if ($current_field !== '') { + $fields[] = trim($current_field); + } + + // Clean up headers - remove quotes and trim whitespace + $headers = array_map(function ($header) { + return trim($header, '"\''); + }, $fields); + + + return array_values($headers); + } } diff --git a/src/Services/ImportExportSystem/BOMValidationService.php b/src/Services/ImportExportSystem/BOMValidationService.php new file mode 100644 index 000000000..74f81fe36 --- /dev/null +++ b/src/Services/ImportExportSystem/BOMValidationService.php @@ -0,0 +1,476 @@ +. + */ +namespace App\Services\ImportExportSystem; + +use App\Entity\Parts\Part; +use App\Entity\ProjectSystem\ProjectBOMEntry; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * Service for validating BOM import data with comprehensive validation rules + * and user-friendly error messages. + */ +class BOMValidationService +{ + public function __construct( + private readonly EntityManagerInterface $entityManager, + private readonly TranslatorInterface $translator + ) { + } + + /** + * Validation result structure + */ + public static function createValidationResult(): array + { + return [ + 'errors' => [], + 'warnings' => [], + 'info' => [], + 'is_valid' => true, + 'total_entries' => 0, + 'valid_entries' => 0, + 'invalid_entries' => 0, + ]; + } + + /** + * Validate a single BOM entry with comprehensive checks + */ + public function validateBOMEntry(array $mapped_entry, int $line_number, array $options = []): array + { + $result = [ + 'line_number' => $line_number, + 'errors' => [], + 'warnings' => [], + 'info' => [], + 'is_valid' => true, + ]; + + // Run all validation rules + $this->validateRequiredFields($mapped_entry, $result); + $this->validateDesignatorFormat($mapped_entry, $result); + $this->validateQuantityFormat($mapped_entry, $result); + $this->validateDesignatorQuantityMatch($mapped_entry, $result); + $this->validatePartDBLink($mapped_entry, $result); + $this->validateComponentName($mapped_entry, $result); + $this->validatePackageFormat($mapped_entry, $result); + $this->validateNumericFields($mapped_entry, $result); + + $result['is_valid'] = empty($result['errors']); + + return $result; + } + + /** + * Validate multiple BOM entries and provide summary + */ + public function validateBOMEntries(array $mapped_entries, array $options = []): array + { + $result = self::createValidationResult(); + $result['total_entries'] = count($mapped_entries); + + $line_results = []; + $all_errors = []; + $all_warnings = []; + $all_info = []; + + foreach ($mapped_entries as $index => $entry) { + $line_number = $index + 1; + $line_result = $this->validateBOMEntry($entry, $line_number, $options); + + $line_results[] = $line_result; + + if ($line_result['is_valid']) { + $result['valid_entries']++; + } else { + $result['invalid_entries']++; + } + + // Collect all messages + $all_errors = array_merge($all_errors, $line_result['errors']); + $all_warnings = array_merge($all_warnings, $line_result['warnings']); + $all_info = array_merge($all_info, $line_result['info']); + } + + // Add summary messages + $this->addSummaryMessages($result, $all_errors, $all_warnings, $all_info); + + $result['errors'] = $all_errors; + $result['warnings'] = $all_warnings; + $result['info'] = $all_info; + $result['line_results'] = $line_results; + $result['is_valid'] = empty($all_errors); + + return $result; + } + + /** + * Validate required fields are present + */ + private function validateRequiredFields(array $entry, array &$result): void + { + $required_fields = ['Designator', 'Quantity']; + + foreach ($required_fields as $field) { + if (!isset($entry[$field]) || trim($entry[$field]) === '') { + $result['errors'][] = $this->translator->trans('project.bom_import.validation.errors.required_field_missing', [ + '%line%' => $result['line_number'], + '%field%' => $field + ]); + } + } + } + + /** + * Validate designator format and content + */ + private function validateDesignatorFormat(array $entry, array &$result): void + { + if (!isset($entry['Designator']) || trim($entry['Designator']) === '') { + return; // Already handled by required fields validation + } + + $designator = trim($entry['Designator']); + $mountnames = array_map('trim', explode(',', $designator)); + + // Remove empty entries + $mountnames = array_filter($mountnames, fn($name) => !empty($name)); + + if (empty($mountnames)) { + $result['errors'][] = $this->translator->trans('project.bom_import.validation.errors.no_valid_designators', [ + '%line%' => $result['line_number'] + ]); + return; + } + + // Validate each mountname format (allow 1-2 uppercase letters, followed by 1+ digits) + $invalid_mountnames = []; + foreach ($mountnames as $mountname) { + if (!preg_match('/^[A-Z]{1,2}[0-9]+$/', $mountname)) { + $invalid_mountnames[] = $mountname; + } + } + + if (!empty($invalid_mountnames)) { + $result['warnings'][] = $this->translator->trans('project.bom_import.validation.warnings.unusual_designator_format', [ + '%line%' => $result['line_number'], + '%designators%' => implode(', ', $invalid_mountnames) + ]); + } + + // Check for duplicate mountnames within the same line + $duplicates = array_diff_assoc($mountnames, array_unique($mountnames)); + if (!empty($duplicates)) { + $result['errors'][] = $this->translator->trans('project.bom_import.validation.errors.duplicate_designators', [ + '%line%' => $result['line_number'], + '%designators%' => implode(', ', array_unique($duplicates)) + ]); + } + } + + /** + * Validate quantity format and value + */ + private function validateQuantityFormat(array $entry, array &$result): void + { + if (!isset($entry['Quantity']) || trim($entry['Quantity']) === '') { + return; // Already handled by required fields validation + } + + $quantity_str = trim($entry['Quantity']); + + // Check if it's a valid number + if (!is_numeric($quantity_str)) { + $result['errors'][] = $this->translator->trans('project.bom_import.validation.errors.invalid_quantity', [ + '%line%' => $result['line_number'], + '%quantity%' => $quantity_str + ]); + return; + } + + $quantity = (float) $quantity_str; + + // Check for reasonable quantity values + if ($quantity <= 0) { + $result['errors'][] = $this->translator->trans('project.bom_import.validation.errors.quantity_zero_or_negative', [ + '%line%' => $result['line_number'], + '%quantity%' => $quantity_str + ]); + } elseif ($quantity > 10000) { + $result['warnings'][] = $this->translator->trans('project.bom_import.validation.warnings.quantity_unusually_high', [ + '%line%' => $result['line_number'], + '%quantity%' => $quantity_str + ]); + } + + // Check if quantity is a whole number when it should be + if (isset($entry['Designator'])) { + $designator = trim($entry['Designator']); + $mountnames = array_map('trim', explode(',', $designator)); + $mountnames = array_filter($mountnames, fn($name) => !empty($name)); + + if (count($mountnames) > 0 && $quantity != (int) $quantity) { + $result['warnings'][] = $this->translator->trans('project.bom_import.validation.warnings.quantity_not_whole_number', [ + '%line%' => $result['line_number'], + '%quantity%' => $quantity_str, + '%count%' => count($mountnames) + ]); + } + } + } + + /** + * Validate that designator count matches quantity + */ + private function validateDesignatorQuantityMatch(array $entry, array &$result): void + { + if (!isset($entry['Designator']) || !isset($entry['Quantity'])) { + return; // Already handled by required fields validation + } + + $designator = trim($entry['Designator']); + $quantity_str = trim($entry['Quantity']); + + if (!is_numeric($quantity_str)) { + return; // Already handled by quantity validation + } + + $mountnames = array_map('trim', explode(',', $designator)); + $mountnames = array_filter($mountnames, fn($name) => !empty($name)); + $mountnames_count = count($mountnames); + $quantity = (float) $quantity_str; + + if ($mountnames_count !== (int) $quantity) { + $result['errors'][] = $this->translator->trans('project.bom_import.validation.errors.quantity_designator_mismatch', [ + '%line%' => $result['line_number'], + '%quantity%' => $quantity_str, + '%count%' => $mountnames_count, + '%designators%' => $designator + ]); + } + } + + /** + * Validate Part-DB ID link + */ + private function validatePartDBLink(array $entry, array &$result): void + { + if (!isset($entry['Part-DB ID']) || trim($entry['Part-DB ID']) === '') { + return; + } + + $part_db_id = trim($entry['Part-DB ID']); + + if (!is_numeric($part_db_id)) { + $result['errors'][] = $this->translator->trans('project.bom_import.validation.errors.invalid_partdb_id', [ + '%line%' => $result['line_number'], + '%id%' => $part_db_id + ]); + return; + } + + $part_id = (int) $part_db_id; + + if ($part_id <= 0) { + $result['errors'][] = $this->translator->trans('project.bom_import.validation.errors.partdb_id_zero_or_negative', [ + '%line%' => $result['line_number'], + '%id%' => $part_id + ]); + return; + } + + // Check if part exists in database + $existing_part = $this->entityManager->getRepository(Part::class)->find($part_id); + if (!$existing_part) { + $result['warnings'][] = $this->translator->trans('project.bom_import.validation.warnings.partdb_id_not_found', [ + '%line%' => $result['line_number'], + '%id%' => $part_id + ]); + } else { + $result['info'][] = $this->translator->trans('project.bom_import.validation.info.partdb_link_success', [ + '%line%' => $result['line_number'], + '%name%' => $existing_part->getName(), + '%id%' => $part_id + ]); + } + } + + /** + * Validate component name/designation + */ + private function validateComponentName(array $entry, array &$result): void + { + $name_fields = ['MPN', 'Designation', 'Value']; + $has_name = false; + + foreach ($name_fields as $field) { + if (isset($entry[$field]) && trim($entry[$field]) !== '') { + $has_name = true; + break; + } + } + + if (!$has_name) { + $result['warnings'][] = $this->translator->trans('project.bom_import.validation.warnings.no_component_name', [ + '%line%' => $result['line_number'] + ]); + } + } + + /** + * Validate package format + */ + private function validatePackageFormat(array $entry, array &$result): void + { + if (!isset($entry['Package']) || trim($entry['Package']) === '') { + return; + } + + $package = trim($entry['Package']); + + // Check for common package format issues + if (strlen($package) > 100) { + $result['warnings'][] = $this->translator->trans('project.bom_import.validation.warnings.package_name_too_long', [ + '%line%' => $result['line_number'], + '%package%' => $package + ]); + } + + // Check for library prefixes (KiCad format) + if (str_contains($package, ':')) { + $result['info'][] = $this->translator->trans('project.bom_import.validation.info.library_prefix_detected', [ + '%line%' => $result['line_number'], + '%package%' => $package + ]); + } + } + + /** + * Validate numeric fields + */ + private function validateNumericFields(array $entry, array &$result): void + { + $numeric_fields = ['Quantity', 'Part-DB ID']; + + foreach ($numeric_fields as $field) { + if (isset($entry[$field]) && trim($entry[$field]) !== '') { + $value = trim($entry[$field]); + if (!is_numeric($value)) { + $result['errors'][] = $this->translator->trans('project.bom_import.validation.errors.non_numeric_field', [ + '%line%' => $result['line_number'], + '%field%' => $field, + '%value%' => $value + ]); + } + } + } + } + + /** + * Add summary messages to validation result + */ + private function addSummaryMessages(array &$result, array $errors, array $warnings, array $info): void + { + $total_entries = $result['total_entries']; + $valid_entries = $result['valid_entries']; + $invalid_entries = $result['invalid_entries']; + + // Add summary info + if ($total_entries > 0) { + $result['info'][] = $this->translator->trans('project.bom_import.validation.info.import_summary', [ + '%total%' => $total_entries, + '%valid%' => $valid_entries, + '%invalid%' => $invalid_entries + ]); + } + + // Add error summary + if (!empty($errors)) { + $error_count = count($errors); + $result['errors'][] = $this->translator->trans('project.bom_import.validation.errors.summary', [ + '%count%' => $error_count + ]); + } + + // Add warning summary + if (!empty($warnings)) { + $warning_count = count($warnings); + $result['warnings'][] = $this->translator->trans('project.bom_import.validation.warnings.summary', [ + '%count%' => $warning_count + ]); + } + + // Add success message if all entries are valid + if ($total_entries > 0 && $invalid_entries === 0) { + $result['info'][] = $this->translator->trans('project.bom_import.validation.info.all_valid'); + } + } + + /** + * Get user-friendly error message for a validation result + */ + public function getErrorMessage(array $validation_result): string + { + if ($validation_result['is_valid']) { + return ''; + } + + $messages = []; + + if (!empty($validation_result['errors'])) { + $messages[] = 'Errors:'; + foreach ($validation_result['errors'] as $error) { + $messages[] = 'โ€ข ' . $error; + } + } + + if (!empty($validation_result['warnings'])) { + $messages[] = 'Warnings:'; + foreach ($validation_result['warnings'] as $warning) { + $messages[] = 'โ€ข ' . $warning; + } + } + + return implode("\n", $messages); + } + + /** + * Get validation statistics + */ + public function getValidationStats(array $validation_result): array + { + return [ + 'total_entries' => $validation_result['total_entries'] ?? 0, + 'valid_entries' => $validation_result['valid_entries'] ?? 0, + 'invalid_entries' => $validation_result['invalid_entries'] ?? 0, + 'error_count' => count($validation_result['errors'] ?? []), + 'warning_count' => count($validation_result['warnings'] ?? []), + 'info_count' => count($validation_result['info'] ?? []), + 'success_rate' => $validation_result['total_entries'] > 0 + ? round(($validation_result['valid_entries'] / $validation_result['total_entries']) * 100, 1) + : 0, + ]; + } +} \ No newline at end of file diff --git a/src/Services/ImportExportSystem/EntityExporter.php b/src/Services/ImportExportSystem/EntityExporter.php index c37db50c0..70feb8e67 100644 --- a/src/Services/ImportExportSystem/EntityExporter.php +++ b/src/Services/ImportExportSystem/EntityExporter.php @@ -38,6 +38,9 @@ use Symfony\Component\HttpFoundation\ResponseHeaderBag; use Symfony\Component\Serializer\SerializerInterface; use function Symfony\Component\String\u; +use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Writer\Xlsx; +use PhpOffice\PhpSpreadsheet\Writer\Xls; /** * Use this class to export an entity to multiple file formats. @@ -52,7 +55,7 @@ public function __construct(protected SerializerInterface $serializer) protected function configureOptions(OptionsResolver $resolver): void { $resolver->setDefault('format', 'csv'); - $resolver->setAllowedValues('format', ['csv', 'json', 'xml', 'yaml']); + $resolver->setAllowedValues('format', ['csv', 'json', 'xml', 'yaml', 'xlsx', 'xls']); $resolver->setDefault('csv_delimiter', ';'); $resolver->setAllowedTypes('csv_delimiter', 'string'); @@ -88,28 +91,35 @@ public function exportEntities(AbstractNamedDBElement|array $entities, array $op $options = $resolver->resolve($options); + //Handle Excel formats by converting from CSV + if (in_array($options['format'], ['xlsx', 'xls'], true)) { + return $this->exportToExcel($entities, $options); + } + //If include children is set, then we need to add the include_children group $groups = [$options['level']]; if ($options['include_children']) { $groups[] = 'include_children'; } - return $this->serializer->serialize($entities, $options['format'], + return $this->serializer->serialize( + $entities, + $options['format'], [ 'groups' => $groups, 'as_collection' => true, 'csv_delimiter' => $options['csv_delimiter'], 'xml_root_node_name' => 'PartDBExport', 'partdb_export' => true, - //Skip the item normalizer, so that we dont get IRIs in the output + //Skip the item normalizer, so that we dont get IRIs in the output SkippableItemNormalizer::DISABLE_ITEM_NORMALIZER => true, - //Handle circular references + //Handle circular references AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => $this->handleCircularReference(...), ] ); } - private function handleCircularReference(object $object, string $format, array $context): string + private function handleCircularReference(object $object): string { if ($object instanceof AbstractStructuralDBElement) { return $object->getFullPath("->"); @@ -119,7 +129,75 @@ private function handleCircularReference(object $object, string $format, array $ return $object->__toString(); } - throw new CircularReferenceException('Circular reference detected for object of type '.get_class($object)); + throw new CircularReferenceException('Circular reference detected for object of type ' . get_class($object)); + } + + /** + * Exports entities to Excel format (xlsx or xls). + * + * @param AbstractNamedDBElement[] $entities The entities to export + * @param array $options The export options + * + * @return string The Excel file content as binary string + */ + protected function exportToExcel(array $entities, array $options): string + { + //First get CSV data using existing serializer + $groups = [$options['level']]; + if ($options['include_children']) { + $groups[] = 'include_children'; + } + + $csvData = $this->serializer->serialize( + $entities, + 'csv', + [ + 'groups' => $groups, + 'as_collection' => true, + 'csv_delimiter' => $options['csv_delimiter'], + 'partdb_export' => true, + SkippableItemNormalizer::DISABLE_ITEM_NORMALIZER => true, + AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => $this->handleCircularReference(...), + ] + ); + + //Convert CSV to Excel + $spreadsheet = new Spreadsheet(); + $worksheet = $spreadsheet->getActiveSheet(); + + $rows = explode("\n", $csvData); + $rowIndex = 1; + + foreach ($rows as $row) { + if (trim($row) === '') { + continue; + } + + $columns = str_getcsv($row, $options['csv_delimiter'], '"', '\\'); + $colIndex = 1; + + foreach ($columns as $column) { + $cellCoordinate = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($colIndex) . $rowIndex; + $worksheet->setCellValue($cellCoordinate, $column); + $colIndex++; + } + $rowIndex++; + } + + //Save to memory stream + $writer = $options['format'] === 'xlsx' ? new Xlsx($spreadsheet) : new Xls($spreadsheet); + + $memFile = fopen("php://temp", 'r+b'); + $writer->save($memFile); + rewind($memFile); + $content = stream_get_contents($memFile); + fclose($memFile); + + if ($content === false) { + throw new \RuntimeException('Failed to read Excel content from memory stream.'); + } + + return $content; } /** @@ -137,7 +215,7 @@ public function exportEntityFromRequest(AbstractNamedDBElement|array $entities, $options = [ 'format' => $request->get('format') ?? 'json', 'level' => $request->get('level') ?? 'extended', - 'include_children' => $request->request->getBoolean('include_children') ?? false, + 'include_children' => $request->request->getBoolean('include_children'), ]; if (!is_array($entities)) { @@ -156,19 +234,15 @@ public function exportEntityFromRequest(AbstractNamedDBElement|array $entities, //Determine the content type for the response - //Plain text should work for all types - $content_type = 'text/plain'; - //Try to use better content types based on the format $format = $options['format']; - switch ($format) { - case 'xml': - $content_type = 'application/xml'; - break; - case 'json': - $content_type = 'application/json'; - break; - } + $content_type = match ($format) { + 'xml' => 'application/xml', + 'json' => 'application/json', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xls' => 'application/vnd.ms-excel', + default => 'text/plain', + }; $response->headers->set('Content-Type', $content_type); //If view option is not specified, then download the file. @@ -186,7 +260,7 @@ public function exportEntityFromRequest(AbstractNamedDBElement|array $entities, $level = $options['level']; - $filename = 'export_'.$entity_name.'_'.$level.'.'.$format; + $filename = "export_{$entity_name}_{$level}.{$format}"; //Sanitize the filename $filename = FilenameSanatizer::sanitizeFilename($filename); diff --git a/src/Services/ImportExportSystem/EntityImporter.php b/src/Services/ImportExportSystem/EntityImporter.php index fb12c3bc2..459866ba5 100644 --- a/src/Services/ImportExportSystem/EntityImporter.php +++ b/src/Services/ImportExportSystem/EntityImporter.php @@ -38,6 +38,9 @@ use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Validator\Validator\ValidatorInterface; +use PhpOffice\PhpSpreadsheet\IOFactory; +use PhpOffice\PhpSpreadsheet\Spreadsheet; +use Psr\Log\LoggerInterface; /** * @see \App\Tests\Services\ImportExportSystem\EntityImporterTest @@ -50,13 +53,14 @@ class EntityImporter */ private const ENCODINGS = ["ASCII", "UTF-8", "ISO-8859-1", "ISO-8859-15", "Windows-1252", "UTF-16", "UTF-32"]; - public function __construct(protected SerializerInterface $serializer, protected EntityManagerInterface $em, protected ValidatorInterface $validator) + public function __construct(protected SerializerInterface $serializer, protected EntityManagerInterface $em, protected ValidatorInterface $validator, protected LoggerInterface $logger) { } /** * Creates many entries at once, based on a (text) list of name. * The created entities are not persisted to database yet, so you have to do it yourself. + * It returns all entities in the hierachy chain (even if they are already persisted). * * @template T of AbstractNamedDBElement * @param string $lines The list of names seperated by \n @@ -101,7 +105,7 @@ public function massCreation(string $lines, string $class_name, ?AbstractStructu foreach ($names as $name) { //Count indentation level (whitespace characters at the beginning of the line) - $identSize = strlen($name)-strlen(ltrim($name)); + $identSize = strlen($name) - strlen(ltrim($name)); //If the line is intended more than the last line, we have a new parent element if ($identSize > end($indentations)) { @@ -132,32 +136,38 @@ public function massCreation(string $lines, string $class_name, ?AbstractStructu //We can only use the getNewEntityFromPath function, if the repository is a StructuralDBElementRepository if ($repo instanceof StructuralDBElementRepository) { $entities = $repo->getNewEntityFromPath($new_path); - $entity = end($entities); - if ($entity === false) { + if ($entities === []) { throw new InvalidArgumentException('getNewEntityFromPath returned an empty array!'); } } else { //Otherwise just create a new entity $entity = new $class_name; $entity->setName($name); + $entities = [$entity]; } //Validate entity - $tmp = $this->validator->validate($entity); - //If no error occured, write entry to DB: - if (0 === count($tmp)) { - $valid_entities[] = $entity; - } else { //Otherwise log error - $errors[] = [ - 'entity' => $entity, - 'violations' => $tmp, - ]; + foreach ($entities as $entity) { + $tmp = $this->validator->validate($entity); + //If no error occured, write entry to DB: + if (0 === count($tmp)) { + $valid_entities[] = $entity; + } else { //Otherwise log error + $errors[] = [ + 'entity' => $entity, + 'violations' => $tmp, + ]; + } } - $last_element = $entity; + $last_element = end($entities); + if ($last_element === false) { + $last_element = null; + } } - return $valid_entities; + //Only return objects once + return array_values(array_unique($valid_entities)); } /** @@ -188,16 +198,20 @@ public function importString(string $data, array $options = [], array &$errors = } //The [] behind class_name denotes that we expect an array. - $entities = $this->serializer->deserialize($data, $options['class'].'[]', $options['format'], + $entities = $this->serializer->deserialize( + $data, + $options['class'] . '[]', + $options['format'], [ 'groups' => $groups, 'csv_delimiter' => $options['csv_delimiter'], 'create_unknown_datastructures' => $options['create_unknown_datastructures'], 'path_delimiter' => $options['path_delimiter'], 'partdb_import' => true, - //Disable API Platform normalizer, as we don't want to use it here + //Disable API Platform normalizer, as we don't want to use it here SkippableItemNormalizer::DISABLE_ITEM_NORMALIZER => true, - ]); + ] + ); //Ensure we have an array of entity elements. if (!is_array($entities)) { @@ -272,7 +286,7 @@ protected function configureOptions(OptionsResolver $resolver): OptionsResolver 'path_delimiter' => '->', //The delimiter used to separate the path elements in the name of a structural element ]); - $resolver->setAllowedValues('format', ['csv', 'json', 'xml', 'yaml']); + $resolver->setAllowedValues('format', ['csv', 'json', 'xml', 'yaml', 'xlsx', 'xls']); $resolver->setAllowedTypes('csv_delimiter', 'string'); $resolver->setAllowedTypes('preserve_children', 'bool'); $resolver->setAllowedTypes('class', 'string'); @@ -328,6 +342,33 @@ public function importFileAndPersistToDB(File $file, array $options = [], array */ public function importFile(File $file, array $options = [], array &$errors = []): array { + $resolver = new OptionsResolver(); + $this->configureOptions($resolver); + $options = $resolver->resolve($options); + + if (in_array($options['format'], ['xlsx', 'xls'], true)) { + $this->logger->info('Converting Excel file to CSV', [ + 'filename' => $file->getFilename(), + 'format' => $options['format'], + 'delimiter' => $options['csv_delimiter'] + ]); + + $csvData = $this->convertExcelToCsv($file, $options['csv_delimiter']); + $options['format'] = 'csv'; + + $this->logger->debug('Excel to CSV conversion completed', [ + 'csv_length' => strlen($csvData), + 'csv_lines' => substr_count($csvData, "\n") + 1 + ]); + + // Log the converted CSV for debugging (first 1000 characters) + $this->logger->debug('Converted CSV preview', [ + 'csv_preview' => substr($csvData, 0, 1000) . (strlen($csvData) > 1000 ? '...' : '') + ]); + + return $this->importString($csvData, $options, $errors); + } + return $this->importString($file->getContent(), $options, $errors); } @@ -347,17 +388,110 @@ public function determineFormat(string $extension): ?string 'xml' => 'xml', 'csv', 'tsv' => 'csv', 'yaml', 'yml' => 'yaml', + 'xlsx' => 'xlsx', + 'xls' => 'xls', default => null, }; } + /** + * Converts Excel file to CSV format using PhpSpreadsheet. + * + * @param File $file The Excel file to convert + * @param string $delimiter The CSV delimiter to use + * + * @return string The CSV data as string + */ + protected function convertExcelToCsv(File $file, string $delimiter = ';'): string + { + try { + $this->logger->debug('Loading Excel file', ['path' => $file->getPathname()]); + $spreadsheet = IOFactory::load($file->getPathname()); + $worksheet = $spreadsheet->getActiveSheet(); + + $csvData = []; + $highestRow = $worksheet->getHighestRow(); + $highestColumn = $worksheet->getHighestColumn(); + + $this->logger->debug('Excel file dimensions', [ + 'rows' => $highestRow, + 'columns_detected' => $highestColumn, + 'worksheet_title' => $worksheet->getTitle() + ]); + + $highestColumnIndex = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($highestColumn); + + for ($row = 1; $row <= $highestRow; $row++) { + $rowData = []; + + // Read all columns using numeric index + for ($colIndex = 1; $colIndex <= $highestColumnIndex; $colIndex++) { + $col = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($colIndex); + try { + $cellValue = $worksheet->getCell("{$col}{$row}")->getCalculatedValue(); + $rowData[] = $cellValue ?? ''; + + } catch (\Exception $e) { + $this->logger->warning('Error reading cell value', [ + 'cell' => "{$col}{$row}", + 'error' => $e->getMessage() + ]); + $rowData[] = ''; + } + } + + $csvRow = implode($delimiter, array_map(function ($value) use ($delimiter) { + $value = (string) $value; + if (strpos($value, $delimiter) !== false || strpos($value, '"') !== false || strpos($value, "\n") !== false) { + return '"' . str_replace('"', '""', $value) . '"'; + } + return $value; + }, $rowData)); + + $csvData[] = $csvRow; + + // Log first few rows for debugging + if ($row <= 3) { + $this->logger->debug("Row {$row} converted", [ + 'original_data' => $rowData, + 'csv_row' => $csvRow, + 'first_cell_raw' => $worksheet->getCell("A{$row}")->getValue(), + 'first_cell_calculated' => $worksheet->getCell("A{$row}")->getCalculatedValue() + ]); + } + } + + $result = implode("\n", $csvData); + + $this->logger->info('Excel to CSV conversion successful', [ + 'total_rows' => count($csvData), + 'total_characters' => strlen($result) + ]); + + $this->logger->debug('Full CSV data', [ + 'csv_data' => $result + ]); + + return $result; + + } catch (\Exception $e) { + $this->logger->error('Failed to convert Excel to CSV', [ + 'file' => $file->getFilename(), + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + throw $e; + } + } + + /** * This functions corrects the parent setting based on the children value of the parent. * * @param iterable $entities the list of entities that should be fixed * @param AbstractStructuralDBElement|null $parent the parent, to which the entity should be set */ - protected function correctParentEntites(iterable $entities, AbstractStructuralDBElement $parent = null): void + protected function correctParentEntites(iterable $entities, ?AbstractStructuralDBElement $parent = null): void { foreach ($entities as $entity) { /** @var AbstractStructuralDBElement $entity */ diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKDatastructureImporter.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKDatastructureImporter.php index 1f842c23d..9e674f05c 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/PKDatastructureImporter.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKDatastructureImporter.php @@ -29,6 +29,7 @@ use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; +use App\Entity\Parts\PartCustomState; use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use Doctrine\ORM\EntityManagerInterface; @@ -148,6 +149,26 @@ public function importPartUnits(array $data): int return is_countable($partunit_data) ? count($partunit_data) : 0; } + public function importPartCustomStates(array $data): int + { + if (!isset($data['partcustomstate'])) { + throw new \RuntimeException('$data must contain a "partcustomstate" key!'); + } + + $partCustomStateData = $data['partcustomstate']; + foreach ($partCustomStateData as $partCustomState) { + $customState = new PartCustomState(); + $customState->setName($partCustomState['name']); + + $this->setIDOfEntity($customState, $partCustomState['id']); + $this->em->persist($customState); + } + + $this->em->flush(); + + return is_countable($partCustomStateData) ? count($partCustomStateData) : 0; + } + public function importCategories(array $data): int { if (!isset($data['partcategory'])) { diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php index 5489fc8cd..1e4cd3bad 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php @@ -105,7 +105,7 @@ protected function convertAttachmentDataToEntity(array $attachment_row, string $ //Next comes the filename plus extension $path .= '/'.$attachment_row['filename'].'.'.$attachment_row['extension']; - $attachment->setPath($path); + $attachment->setInternalPath($path); return $attachment; } diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php index 9dd67233f..ab06a1346 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php @@ -35,6 +35,7 @@ use App\Entity\PriceInformations\Currency; use App\Entity\PriceInformations\Orderdetail; use App\Entity\PriceInformations\Pricedetail; +use App\Settings\SystemSettings\LocalizationSettings; use Brick\Math\BigDecimal; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Intl\Currencies; @@ -47,7 +48,7 @@ class PKPartImporter { use PKImportHelperTrait; - public function __construct(EntityManagerInterface $em, PropertyAccessorInterface $propertyAccessor, private readonly string $base_currency) + public function __construct(EntityManagerInterface $em, PropertyAccessorInterface $propertyAccessor, private readonly LocalizationSettings $localizationSettings) { $this->em = $em; $this->propertyAccessor = $propertyAccessor; @@ -90,6 +91,8 @@ public function importParts(array $data): int $this->setAssociationField($entity, 'partUnit', MeasurementUnit::class, $part['partUnit_id']); } + $this->setAssociationField($entity, 'partCustomState', MeasurementUnit::class, $part['partCustomState_id']); + //Create a part lot to store the stock level and location $lot = new PartLot(); $lot->setAmount((float) ($part['stockLevel'] ?? 0)); @@ -210,7 +213,7 @@ protected function getOrCreateCurrency(string $currency_iso_code): ?Currency $currency_iso_code = strtoupper($currency_iso_code); //We do not have a currency for the base currency to be consistent with prices without currencies - if ($currency_iso_code === $this->base_currency) { + if ($currency_iso_code === $this->localizationSettings->baseCurrency) { return null; } diff --git a/src/Services/InfoProviderSystem/BulkInfoProviderService.php b/src/Services/InfoProviderSystem/BulkInfoProviderService.php new file mode 100644 index 000000000..586fb8737 --- /dev/null +++ b/src/Services/InfoProviderSystem/BulkInfoProviderService.php @@ -0,0 +1,380 @@ + Cache for normalized supplier names */ + private array $supplierCache = []; + + public function __construct( + private readonly PartInfoRetriever $infoRetriever, + private readonly ExistingPartFinder $existingPartFinder, + private readonly ProviderRegistry $providerRegistry, + private readonly EntityManagerInterface $entityManager, + private readonly LoggerInterface $logger + ) {} + + /** + * Perform bulk search across multiple parts and providers. + * + * @param Part[] $parts Array of parts to search for + * @param BulkSearchFieldMappingDTO[] $fieldMappings Array of field mappings defining search strategy + * @param bool $prefetchDetails Whether to prefetch detailed information for results + * @return BulkSearchResponseDTO Structured response containing all search results + * @throws \InvalidArgumentException If no valid parts provided + * @throws \RuntimeException If no search results found for any parts + */ + public function performBulkSearch(array $parts, array $fieldMappings, bool $prefetchDetails = false): BulkSearchResponseDTO + { + if (empty($parts)) { + throw new \InvalidArgumentException('No valid parts found for bulk import'); + } + + $partResults = []; + $hasAnyResults = false; + + // Group providers by batch capability + $batchProviders = []; + $regularProviders = []; + + foreach ($fieldMappings as $mapping) { + foreach ($mapping->providers as $providerKey) { + if (!is_string($providerKey)) { + $this->logger->error('Invalid provider key type', [ + 'providerKey' => $providerKey, + 'type' => gettype($providerKey) + ]); + continue; + } + + $provider = $this->providerRegistry->getProviderByKey($providerKey); + if ($provider instanceof BatchInfoProviderInterface) { + $batchProviders[$providerKey] = $provider; + } else { + $regularProviders[$providerKey] = $provider; + } + } + } + + // Process batch providers first (more efficient) + $batchResults = $this->processBatchProviders($parts, $fieldMappings, $batchProviders); + + // Process regular providers + $regularResults = $this->processRegularProviders($parts, $fieldMappings, $regularProviders, $batchResults); + + // Combine and format results for each part + foreach ($parts as $part) { + $searchResults = []; + + // Get results from batch and regular processing + $allResults = array_merge( + $batchResults[$part->getId()] ?? [], + $regularResults[$part->getId()] ?? [] + ); + + if (!empty($allResults)) { + $hasAnyResults = true; + $searchResults = $this->formatSearchResults($allResults); + } + + $partResults[] = new BulkSearchPartResultsDTO( + part: $part, + searchResults: $searchResults, + errors: [] + ); + } + + if (!$hasAnyResults) { + throw new \RuntimeException('No search results found for any of the selected parts'); + } + + $response = new BulkSearchResponseDTO($partResults); + + // Prefetch details if requested + if ($prefetchDetails) { + $this->prefetchDetailsForResults($response); + } + + return $response; + } + + /** + * Process parts using batch-capable info providers. + * + * @param Part[] $parts Array of parts to search for + * @param BulkSearchFieldMappingDTO[] $fieldMappings Array of field mapping configurations + * @param array $batchProviders Batch providers indexed by key + * @return array Results indexed by part ID + */ + private function processBatchProviders(array $parts, array $fieldMappings, array $batchProviders): array + { + $batchResults = []; + + foreach ($batchProviders as $providerKey => $provider) { + $keywords = $this->collectKeywordsForProvider($parts, $fieldMappings, $providerKey); + + if (empty($keywords)) { + continue; + } + + try { + $providerResults = $provider->searchByKeywordsBatch($keywords); + + // Map results back to parts + foreach ($parts as $part) { + foreach ($fieldMappings as $mapping) { + if (!in_array($providerKey, $mapping->providers, true)) { + continue; + } + + $keyword = $this->getKeywordFromField($part, $mapping->field); + if ($keyword && isset($providerResults[$keyword])) { + foreach ($providerResults[$keyword] as $dto) { + $batchResults[$part->getId()][] = new BulkSearchPartResultDTO( + searchResult: $dto, + sourceField: $mapping->field, + sourceKeyword: $keyword, + localPart: $this->existingPartFinder->findFirstExisting($dto), + priority: $mapping->priority + ); + } + } + } + } + } catch (\Exception $e) { + $this->logger->error('Batch search failed for provider ' . $providerKey, [ + 'error' => $e->getMessage(), + 'provider' => $providerKey + ]); + } + } + + return $batchResults; + } + + /** + * Process parts using regular (non-batch) info providers. + * + * @param Part[] $parts Array of parts to search for + * @param BulkSearchFieldMappingDTO[] $fieldMappings Array of field mapping configurations + * @param array $regularProviders Regular providers indexed by key + * @param array $excludeResults Results to exclude (from batch processing) + * @return array Results indexed by part ID + */ + private function processRegularProviders(array $parts, array $fieldMappings, array $regularProviders, array $excludeResults): array + { + $regularResults = []; + + foreach ($parts as $part) { + $regularResults[$part->getId()] = []; + + // Skip if we already have batch results for this part + if (!empty($excludeResults[$part->getId()] ?? [])) { + continue; + } + + foreach ($fieldMappings as $mapping) { + $providers = array_intersect($mapping->providers, array_keys($regularProviders)); + + if (empty($providers)) { + continue; + } + + $keyword = $this->getKeywordFromField($part, $mapping->field); + if (!$keyword) { + continue; + } + + try { + $dtos = $this->infoRetriever->searchByKeyword($keyword, $providers); + + foreach ($dtos as $dto) { + $regularResults[$part->getId()][] = new BulkSearchPartResultDTO( + searchResult: $dto, + sourceField: $mapping->field, + sourceKeyword: $keyword, + localPart: $this->existingPartFinder->findFirstExisting($dto), + priority: $mapping->priority + ); + } + } catch (ClientException $e) { + $this->logger->error('Regular search failed', [ + 'part_id' => $part->getId(), + 'field' => $mapping->field, + 'error' => $e->getMessage() + ]); + } + } + } + + return $regularResults; + } + + /** + * Collect unique keywords for a specific provider from all parts and field mappings. + * + * @param Part[] $parts Array of parts to collect keywords from + * @param BulkSearchFieldMappingDTO[] $fieldMappings Array of field mapping configurations + * @param string $providerKey The provider key to collect keywords for + * @return string[] Array of unique keywords + */ + private function collectKeywordsForProvider(array $parts, array $fieldMappings, string $providerKey): array + { + $keywords = []; + + foreach ($parts as $part) { + foreach ($fieldMappings as $mapping) { + if (!in_array($providerKey, $mapping->providers, true)) { + continue; + } + + $keyword = $this->getKeywordFromField($part, $mapping->field); + if ($keyword && !in_array($keyword, $keywords, true)) { + $keywords[] = $keyword; + } + } + } + + return $keywords; + } + + private function getKeywordFromField(Part $part, string $field): ?string + { + return match ($field) { + 'mpn' => $part->getManufacturerProductNumber(), + 'name' => $part->getName(), + default => $this->getSupplierPartNumber($part, $field) + }; + } + + private function getSupplierPartNumber(Part $part, string $field): ?string + { + if (!str_ends_with($field, '_spn')) { + return null; + } + + $supplierKey = substr($field, 0, -4); + $supplier = $this->getSupplierByNormalizedName($supplierKey); + + if (!$supplier) { + return null; + } + + $orderDetail = $part->getOrderdetails()->filter( + fn($od) => $od->getSupplier()?->getId() === $supplier->getId() + )->first(); + + return $orderDetail !== false ? $orderDetail->getSupplierpartnr() : null; + } + + /** + * Get supplier by normalized name with caching to prevent N+1 queries. + * + * @param string $normalizedKey The normalized supplier key to search for + * @return Supplier|null The matching supplier or null if not found + */ + private function getSupplierByNormalizedName(string $normalizedKey): ?Supplier + { + // Check cache first + if (isset($this->supplierCache[$normalizedKey])) { + return $this->supplierCache[$normalizedKey]; + } + + // Use efficient database query with PHP normalization + // Since DQL doesn't support REPLACE, we'll load all suppliers once and cache the normalization + if (empty($this->supplierCache)) { + $this->loadSuppliersIntoCache(); + } + + $supplier = $this->supplierCache[$normalizedKey] ?? null; + + // Cache the result (including null results to prevent repeated queries) + $this->supplierCache[$normalizedKey] = $supplier; + + return $supplier; + } + + /** + * Load all suppliers into cache with normalized names to avoid N+1 queries. + */ + private function loadSuppliersIntoCache(): void + { + /** @var Supplier[] $suppliers */ + $suppliers = $this->entityManager->getRepository(Supplier::class)->findAll(); + + foreach ($suppliers as $supplier) { + $normalizedName = strtolower(str_replace([' ', '-', '_'], '_', $supplier->getName())); + $this->supplierCache[$normalizedName] = $supplier; + } + } + + /** + * Format and deduplicate search results. + * + * @param BulkSearchPartResultDTO[] $bulkResults Array of bulk search results + * @return BulkSearchPartResultDTO[] Array of formatted search results with metadata + */ + private function formatSearchResults(array $bulkResults): array + { + // Sort by priority and remove duplicates + usort($bulkResults, fn($a, $b) => $a->priority <=> $b->priority); + + $uniqueResults = []; + $seenKeys = []; + + foreach ($bulkResults as $result) { + $key = "{$result->searchResult->provider_key}|{$result->searchResult->provider_id}"; + if (!in_array($key, $seenKeys, true)) { + $seenKeys[] = $key; + $uniqueResults[] = $result; + } + } + + return $uniqueResults; + } + + /** + * Prefetch detailed information for search results. + * + * @param BulkSearchResponseDTO $searchResults Search results (supports both new DTO and legacy array format) + */ + public function prefetchDetailsForResults(BulkSearchResponseDTO $searchResults): void + { + $prefetchCount = 0; + + // Handle both new DTO format and legacy array format for backwards compatibility + foreach ($searchResults->partResults as $partResult) { + foreach ($partResult->searchResults as $result) { + $dto = $result->searchResult; + + try { + $this->infoRetriever->getDetails($dto->provider_key, $dto->provider_id); + $prefetchCount++; + } catch (\Exception $e) { + $this->logger->warning('Failed to prefetch details for provider part', [ + 'provider_key' => $dto->provider_key, + 'provider_id' => $dto->provider_id, + 'error' => $e->getMessage() + ]); + } + } + } + + $this->logger->info("Prefetched details for {$prefetchCount} search results"); + } +} diff --git a/src/Services/InfoProviderSystem/DTOs/BulkSearchFieldMappingDTO.php b/src/Services/InfoProviderSystem/DTOs/BulkSearchFieldMappingDTO.php new file mode 100644 index 000000000..50b7f4cfd --- /dev/null +++ b/src/Services/InfoProviderSystem/DTOs/BulkSearchFieldMappingDTO.php @@ -0,0 +1,91 @@ +. + */ + +declare(strict_types=1); + +namespace App\Services\InfoProviderSystem\DTOs; + +/** + * Represents a mapping between a part field and the info providers that should search in that field. + */ +readonly class BulkSearchFieldMappingDTO +{ + /** + * @param string $field The field to search in (e.g., 'mpn', 'name', or supplier-specific fields like 'digikey_spn') + * @param string[] $providers Array of provider keys to search with (e.g., ['digikey', 'farnell']) + * @param int $priority Priority for this field mapping (1-10, lower numbers = higher priority) + */ + public function __construct( + public string $field, + public array $providers, + public int $priority = 1 + ) { + if ($priority < 1 || $priority > 10) { + throw new \InvalidArgumentException('Priority must be between 1 and 10'); + } + } + + /** + * Create a FieldMappingDTO from legacy array format. + * @param array{field: string, providers: string[], priority?: int} $data + */ + public static function fromSerializableArray(array $data): self + { + return new self( + field: $data['field'], + providers: $data['providers'] ?? [], + priority: $data['priority'] ?? 1 + ); + } + + /** + * Convert this DTO to the legacy array format for backwards compatibility. + * @return array{field: string, providers: string[], priority: int} + */ + public function toSerializableArray(): array + { + return [ + 'field' => $this->field, + 'providers' => $this->providers, + 'priority' => $this->priority, + ]; + } + + /** + * Check if this field mapping is for a supplier part number field. + */ + public function isSupplierPartNumberField(): bool + { + return str_ends_with($this->field, '_spn'); + } + + /** + * Get the supplier key from a supplier part number field. + * Returns null if this is not a supplier part number field. + */ + public function getSupplierKey(): ?string + { + if (!$this->isSupplierPartNumberField()) { + return null; + } + + return substr($this->field, 0, -4); + } +} diff --git a/src/Services/InfoProviderSystem/DTOs/BulkSearchPartResultDTO.php b/src/Services/InfoProviderSystem/DTOs/BulkSearchPartResultDTO.php new file mode 100644 index 000000000..d46624d4f --- /dev/null +++ b/src/Services/InfoProviderSystem/DTOs/BulkSearchPartResultDTO.php @@ -0,0 +1,44 @@ +. + */ + +declare(strict_types=1); + +namespace App\Services\InfoProviderSystem\DTOs; + +use App\Entity\Parts\Part; + +/** + * Represents a single search result from bulk search with additional context information, like how the part was found. + */ +readonly class BulkSearchPartResultDTO +{ + public function __construct( + /** The base search result DTO containing provider data */ + public SearchResultDTO $searchResult, + /** The field that was used to find this result */ + public ?string $sourceField = null, + /** The actual keyword that was searched for */ + public ?string $sourceKeyword = null, + /** Local part that matches this search result, if any */ + public ?Part $localPart = null, + /** Priority for this search result */ + public int $priority = 1 + ) {} +} diff --git a/src/Services/InfoProviderSystem/DTOs/BulkSearchPartResultsDTO.php b/src/Services/InfoProviderSystem/DTOs/BulkSearchPartResultsDTO.php new file mode 100644 index 000000000..8614f4ecc --- /dev/null +++ b/src/Services/InfoProviderSystem/DTOs/BulkSearchPartResultsDTO.php @@ -0,0 +1,83 @@ +. + */ + +declare(strict_types=1); + +namespace App\Services\InfoProviderSystem\DTOs; + +use App\Entity\Parts\Part; + +/** + * Represents the search results for a single part from bulk info provider search. + * It contains multiple search results, that match the part. + */ +readonly class BulkSearchPartResultsDTO +{ + /** + * @param Part $part The part that was searched for + * @param BulkSearchPartResultDTO[] $searchResults Array of search results found for this part + * @param string[] $errors Array of error messages encountered during search + */ + public function __construct( + public Part $part, + public array $searchResults = [], + public array $errors = [] + ) {} + + /** + * Check if this part has any search results. + */ + public function hasResults(): bool + { + return !empty($this->searchResults); + } + + /** + * Check if this part has any errors. + */ + public function hasErrors(): bool + { + return !empty($this->errors); + } + + /** + * Get the number of search results for this part. + */ + public function getResultCount(): int + { + return count($this->searchResults); + } + + public function getErrorCount(): int + { + return count($this->errors); + } + + /** + * Get search results sorted by priority (ascending). + * @return BulkSearchPartResultDTO[] + */ + public function getResultsSortedByPriority(): array + { + $results = $this->searchResults; + usort($results, static fn(BulkSearchPartResultDTO $a, BulkSearchPartResultDTO $b) => $a->priority <=> $b->priority); + return $results; + } +} diff --git a/src/Services/InfoProviderSystem/DTOs/BulkSearchResponseDTO.php b/src/Services/InfoProviderSystem/DTOs/BulkSearchResponseDTO.php new file mode 100644 index 000000000..58e9e240e --- /dev/null +++ b/src/Services/InfoProviderSystem/DTOs/BulkSearchResponseDTO.php @@ -0,0 +1,231 @@ +. + */ + +declare(strict_types=1); + +namespace App\Services\InfoProviderSystem\DTOs; + +use App\Entity\Parts\Part; +use Doctrine\ORM\EntityManagerInterface; +use Traversable; + +/** + * Represents the complete response from a bulk info provider search operation. + * It contains a list of PartSearchResultDTOs, one for each part searched. + */ +readonly class BulkSearchResponseDTO implements \ArrayAccess, \IteratorAggregate +{ + /** + * @param BulkSearchPartResultsDTO[] $partResults Array of search results for each part + */ + public function __construct( + public array $partResults + ) {} + + /** + * Replaces the search results for a specific part, and returns a new instance. + * The part to replaced, is identified by the part property of the new_results parameter. + * The original instance remains unchanged. + * @param BulkSearchPartResultsDTO $new_results + * @return BulkSearchResponseDTO + */ + public function replaceResultsForPart(BulkSearchPartResultsDTO $new_results): self + { + $array = $this->partResults; + $replaced = false; + foreach ($array as $index => $partResult) { + if ($partResult->part === $new_results->part) { + $array[$index] = $new_results; + $replaced = true; + break; + } + } + + if (!$replaced) { + throw new \InvalidArgumentException("Part not found in existing results."); + } + + return new self($array); + } + + /** + * Check if any parts have search results. + */ + public function hasAnyResults(): bool + { + foreach ($this->partResults as $partResult) { + if ($partResult->hasResults()) { + return true; + } + } + return false; + } + + /** + * Get the total number of search results across all parts. + */ + public function getTotalResultCount(): int + { + $count = 0; + foreach ($this->partResults as $partResult) { + $count += $partResult->getResultCount(); + } + return $count; + } + + /** + * Get all parts that have search results. + * @return BulkSearchPartResultsDTO[] + */ + public function getPartsWithResults(): array + { + return array_filter($this->partResults, fn($result) => $result->hasResults()); + } + + /** + * Get all parts that have errors. + * @return BulkSearchPartResultsDTO[] + */ + public function getPartsWithErrors(): array + { + return array_filter($this->partResults, fn($result) => $result->hasErrors()); + } + + /** + * Get the number of parts processed. + */ + public function getPartCount(): int + { + return count($this->partResults); + } + + /** + * Get the number of parts with successful results. + */ + public function getSuccessfulPartCount(): int + { + return count($this->getPartsWithResults()); + } + + /** + * Merge multiple BulkSearchResponseDTO instances into one. + * @param BulkSearchResponseDTO ...$responses + * @return BulkSearchResponseDTO + */ + public static function merge(BulkSearchResponseDTO ...$responses): BulkSearchResponseDTO + { + $mergedResults = []; + foreach ($responses as $response) { + foreach ($response->partResults as $partResult) { + $mergedResults[] = $partResult; + } + } + return new BulkSearchResponseDTO($mergedResults); + } + + /** + * Convert this DTO to a serializable representation suitable for storage in the database + * @return array + */ + public function toSerializableRepresentation(): array + { + $serialized = []; + + foreach ($this->partResults as $partResult) { + $partData = [ + 'part_id' => $partResult->part->getId(), + 'search_results' => [], + 'errors' => $partResult->errors ?? [] + ]; + + foreach ($partResult->searchResults as $result) { + $partData['search_results'][] = [ + 'dto' => $result->searchResult->toNormalizedSearchResultArray(), + 'source_field' => $result->sourceField ?? null, + 'source_keyword' => $result->sourceKeyword ?? null, + 'localPart' => $result->localPart?->getId(), + 'priority' => $result->priority + ]; + } + + $serialized[] = $partData; + } + + return $serialized; + } + + /** + * Creates a BulkSearchResponseDTO from a serializable representation. + * @param array $data + * @param EntityManagerInterface $entityManager + * @return BulkSearchResponseDTO + * @throws \Doctrine\ORM\Exception\ORMException + */ + public static function fromSerializableRepresentation(array $data, EntityManagerInterface $entityManager): BulkSearchResponseDTO + { + $partResults = []; + foreach ($data as $partData) { + $partResults[] = new BulkSearchPartResultsDTO( + part: $entityManager->getReference(Part::class, $partData['part_id']), + searchResults: array_map(fn($result) => new BulkSearchPartResultDTO( + searchResult: SearchResultDTO::fromNormalizedSearchResultArray($result['dto']), + sourceField: $result['source_field'] ?? null, + sourceKeyword: $result['source_keyword'] ?? null, + localPart: isset($result['localPart']) ? $entityManager->getReference(Part::class, $result['localPart']) : null, + priority: $result['priority'] ?? null + ), $partData['search_results'] ?? []), + errors: $partData['errors'] ?? [] + ); + } + + return new BulkSearchResponseDTO($partResults); + } + + public function offsetExists(mixed $offset): bool + { + if (!is_int($offset)) { + throw new \InvalidArgumentException("Offset must be an integer."); + } + return isset($this->partResults[$offset]); + } + + public function offsetGet(mixed $offset): ?BulkSearchPartResultsDTO + { + if (!is_int($offset)) { + throw new \InvalidArgumentException("Offset must be an integer."); + } + return $this->partResults[$offset] ?? null; + } + + public function offsetSet(mixed $offset, mixed $value): void + { + throw new \LogicException("BulkSearchResponseDTO is immutable."); + } + + public function offsetUnset(mixed $offset): void + { + throw new \LogicException('BulkSearchResponseDTO is immutable.'); + } + + public function getIterator(): Traversable + { + return new \ArrayIterator($this->partResults); + } +} diff --git a/src/Services/InfoProviderSystem/DTOs/FileDTO.php b/src/Services/InfoProviderSystem/DTOs/FileDTO.php index 0d1db76a1..84eed0c95 100644 --- a/src/Services/InfoProviderSystem/DTOs/FileDTO.php +++ b/src/Services/InfoProviderSystem/DTOs/FileDTO.php @@ -28,12 +28,12 @@ * This could be a datasheet, a 3D model, a picture or similar. * @see \App\Tests\Services\InfoProviderSystem\DTOs\FileDTOTest */ -class FileDTO +readonly class FileDTO { /** * @var string The URL where to get this file */ - public readonly string $url; + public string $url; /** * @param string $url The URL where to get this file @@ -41,7 +41,7 @@ class FileDTO */ public function __construct( string $url, - public readonly ?string $name = null, + public ?string $name = null, ) { //Find all occurrences of non URL safe characters and replace them with their URL encoded version. //We only want to replace characters which can not have a valid meaning in a URL (what would break the URL). @@ -50,4 +50,4 @@ public function __construct( } -} \ No newline at end of file +} diff --git a/src/Services/InfoProviderSystem/DTOs/ParameterDTO.php b/src/Services/InfoProviderSystem/DTOs/ParameterDTO.php index 3332700b0..f5868039e 100644 --- a/src/Services/InfoProviderSystem/DTOs/ParameterDTO.php +++ b/src/Services/InfoProviderSystem/DTOs/ParameterDTO.php @@ -28,17 +28,17 @@ * This could be a voltage, a current, a temperature or similar. * @see \App\Tests\Services\InfoProviderSystem\DTOs\ParameterDTOTest */ -class ParameterDTO +readonly class ParameterDTO { public function __construct( - public readonly string $name, - public readonly ?string $value_text = null, - public readonly ?float $value_typ = null, - public readonly ?float $value_min = null, - public readonly ?float $value_max = null, - public readonly ?string $unit = null, - public readonly ?string $symbol = null, - public readonly ?string $group = null, + public string $name, + public ?string $value_text = null, + public ?float $value_typ = null, + public ?float $value_min = null, + public ?float $value_max = null, + public ?string $unit = null, + public ?string $symbol = null, + public ?string $group = null, ) { } @@ -72,9 +72,9 @@ public static function parseValueField(string $name, string|float $value, ?strin group: $group); } - //If the attribute contains "..." or a tilde we assume it is a range - if (preg_match('/(\.{3}|~)/', $value) === 1) { - $parts = preg_split('/\s*(\.{3}|~)\s*/', $value); + //If the attribute contains ".." or "..." or a tilde we assume it is a range + if (preg_match('/(\.{2,3}|~)/', $value) === 1) { + $parts = preg_split('/\s*(\.{2,3}|~)\s*/', $value); if (count($parts) === 2) { //Try to extract number and unit from value (allow leading +) if ($unit === null || trim($unit) === '') { diff --git a/src/Services/InfoProviderSystem/DTOs/PartDetailDTO.php b/src/Services/InfoProviderSystem/DTOs/PartDetailDTO.php index 9f365f1e6..41d505101 100644 --- a/src/Services/InfoProviderSystem/DTOs/PartDetailDTO.php +++ b/src/Services/InfoProviderSystem/DTOs/PartDetailDTO.php @@ -70,4 +70,4 @@ public function __construct( footprint: $footprint, ); } -} \ No newline at end of file +} diff --git a/src/Services/InfoProviderSystem/DTOs/PriceDTO.php b/src/Services/InfoProviderSystem/DTOs/PriceDTO.php index f1eb28f7f..2acf3e575 100644 --- a/src/Services/InfoProviderSystem/DTOs/PriceDTO.php +++ b/src/Services/InfoProviderSystem/DTOs/PriceDTO.php @@ -28,21 +28,21 @@ /** * This DTO represents a price for a single unit in a certain discount range */ -class PriceDTO +readonly class PriceDTO { - private readonly BigDecimal $price_as_big_decimal; + private BigDecimal $price_as_big_decimal; public function __construct( /** @var float The minimum amount that needs to get ordered for this price to be valid */ - public readonly float $minimum_discount_amount, + public float $minimum_discount_amount, /** @var string The price as string (with .) */ - public readonly string $price, + public string $price, /** @var string The currency of the used ISO code of this price detail */ - public readonly ?string $currency_iso_code, + public ?string $currency_iso_code, /** @var bool If the price includes tax */ - public readonly ?bool $includes_tax = true, + public ?bool $includes_tax = true, /** @var float the price related quantity */ - public readonly ?float $price_related_quantity = 1.0, + public ?float $price_related_quantity = 1.0, ) { $this->price_as_big_decimal = BigDecimal::of($this->price); diff --git a/src/Services/InfoProviderSystem/DTOs/PurchaseInfoDTO.php b/src/Services/InfoProviderSystem/DTOs/PurchaseInfoDTO.php index bcd8be435..9ac142ff6 100644 --- a/src/Services/InfoProviderSystem/DTOs/PurchaseInfoDTO.php +++ b/src/Services/InfoProviderSystem/DTOs/PurchaseInfoDTO.php @@ -27,15 +27,15 @@ * This DTO represents a purchase information for a part (supplier name, order number and prices). * @see \App\Tests\Services\InfoProviderSystem\DTOs\PurchaseInfoDTOTest */ -class PurchaseInfoDTO +readonly class PurchaseInfoDTO { public function __construct( - public readonly string $distributor_name, - public readonly string $order_number, + public string $distributor_name, + public string $order_number, /** @var PriceDTO[] */ - public readonly array $prices, + public array $prices, /** @var string|null An url to the product page of the vendor */ - public readonly ?string $product_url = null, + public ?string $product_url = null, ) { //Ensure that the prices are PriceDTO instances @@ -45,4 +45,4 @@ public function __construct( } } } -} \ No newline at end of file +} diff --git a/src/Services/InfoProviderSystem/DTOs/SearchResultDTO.php b/src/Services/InfoProviderSystem/DTOs/SearchResultDTO.php index 289437020..a70b2486b 100644 --- a/src/Services/InfoProviderSystem/DTOs/SearchResultDTO.php +++ b/src/Services/InfoProviderSystem/DTOs/SearchResultDTO.php @@ -59,8 +59,8 @@ public function __construct( public readonly ?string $provider_url = null, /** @var string|null A footprint representation of the providers page */ public readonly ?string $footprint = null, - ) { - + ) + { if ($preview_image_url !== null) { //Utilize the escaping mechanism of FileDTO to ensure that the preview image URL is correctly encoded //See issue #521: https://github.com/Part-DB/Part-DB-server/issues/521 @@ -71,4 +71,47 @@ public function __construct( $this->preview_image_url = null; } } -} \ No newline at end of file + + /** + * This method creates a normalized array representation of the DTO. + * @return array + */ + public function toNormalizedSearchResultArray(): array + { + return [ + 'provider_key' => $this->provider_key, + 'provider_id' => $this->provider_id, + 'name' => $this->name, + 'description' => $this->description, + 'category' => $this->category, + 'manufacturer' => $this->manufacturer, + 'mpn' => $this->mpn, + 'preview_image_url' => $this->preview_image_url, + 'manufacturing_status' => $this->manufacturing_status?->value, + 'provider_url' => $this->provider_url, + 'footprint' => $this->footprint, + ]; + } + + /** + * Creates a SearchResultDTO from a normalized array representation. + * @param array $data + * @return self + */ + public static function fromNormalizedSearchResultArray(array $data): self + { + return new self( + provider_key: $data['provider_key'], + provider_id: $data['provider_id'], + name: $data['name'], + description: $data['description'], + category: $data['category'] ?? null, + manufacturer: $data['manufacturer'] ?? null, + mpn: $data['mpn'] ?? null, + preview_image_url: $data['preview_image_url'] ?? null, + manufacturing_status: isset($data['manufacturing_status']) ? ManufacturingStatus::tryFrom($data['manufacturing_status']) : null, + provider_url: $data['provider_url'] ?? null, + footprint: $data['footprint'] ?? null, + ); + } +} diff --git a/src/Services/InfoProviderSystem/DTOtoEntityConverter.php b/src/Services/InfoProviderSystem/DTOtoEntityConverter.php index 3c1e4ccab..a655a0dfa 100644 --- a/src/Services/InfoProviderSystem/DTOtoEntityConverter.php +++ b/src/Services/InfoProviderSystem/DTOtoEntityConverter.php @@ -43,6 +43,7 @@ use App\Services\InfoProviderSystem\DTOs\PartDetailDTO; use App\Services\InfoProviderSystem\DTOs\PriceDTO; use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO; +use App\Settings\SystemSettings\LocalizationSettings; use Doctrine\ORM\EntityManagerInterface; /** @@ -54,8 +55,11 @@ final class DTOtoEntityConverter private const TYPE_DATASHEETS_NAME = 'Datasheet'; private const TYPE_IMAGE_NAME = 'Image'; - public function __construct(private readonly EntityManagerInterface $em, private readonly string $base_currency) + private readonly string $base_currency; + + public function __construct(private readonly EntityManagerInterface $em, LocalizationSettings $localizationSettings) { + $this->base_currency = $localizationSettings->baseCurrency; } /** @@ -174,9 +178,21 @@ public function convertPart(PartDetailDTO $dto, Part $entity = new Part()): Part //Set the provider reference on the part $entity->setProviderReference(InfoProviderReference::fromPartDTO($dto)); + $param_groups = []; + //Add parameters foreach ($dto->parameters ?? [] as $parameter) { - $entity->addParameter($this->convertParameter($parameter)); + $new_param = $this->convertParameter($parameter); + + $key = $new_param->getName() . '##' . $new_param->getGroup(); + //If there is already an parameter with the same name and group, rename the new parameter, by suffixing a number + if (count($param_groups[$key] ?? []) > 0) { + $new_param->setName($new_param->getName() . ' (' . (count($param_groups[$key]) + 1) . ')'); + } + + $param_groups[$key][] = $new_param; + + $entity->addParameter($new_param); } //Add preview image @@ -192,6 +208,8 @@ public function convertPart(PartDetailDTO $dto, Part $entity = new Part()): Part $entity->setMasterPictureAttachment($preview_image); } + $attachments_grouped = []; + //Add other images $images = $this->files_unique($dto->images ?? []); foreach ($images as $image) { @@ -200,14 +218,29 @@ public function convertPart(PartDetailDTO $dto, Part $entity = new Part()): Part continue; } - $entity->addAttachment($this->convertFile($image, $image_type)); + $attachment = $this->convertFile($image, $image_type); + + $attachments_grouped[$attachment->getName()][] = $attachment; + if (count($attachments_grouped[$attachment->getName()]) > 1) { + $attachment->setName($attachment->getName() . ' (' . (count($attachments_grouped[$attachment->getName()]) + 1) . ')'); + } + + + $entity->addAttachment($attachment); } //Add datasheets $datasheet_type = $this->getDatasheetType(); $datasheets = $this->files_unique($dto->datasheets ?? []); foreach ($datasheets as $datasheet) { - $entity->addAttachment($this->convertFile($datasheet, $datasheet_type)); + $attachment = $this->convertFile($datasheet, $datasheet_type); + + $attachments_grouped[$attachment->getName()][] = $attachment; + if (count($attachments_grouped[$attachment->getName()]) > 1) { + $attachment->setName($attachment->getName() . ' (' . (count($attachments_grouped[$attachment->getName()])) . ')'); + } + + $entity->addAttachment($attachment); } //Add orderdetails and prices @@ -324,4 +357,4 @@ private function getImageType(): AttachmentType return $tmp; } -} \ No newline at end of file +} diff --git a/src/Services/InfoProviderSystem/ExistingPartFinder.php b/src/Services/InfoProviderSystem/ExistingPartFinder.php index 762c1517a..614ca105c 100644 --- a/src/Services/InfoProviderSystem/ExistingPartFinder.php +++ b/src/Services/InfoProviderSystem/ExistingPartFinder.php @@ -1,5 +1,7 @@ getQuery()->getResult(); } -} \ No newline at end of file +} diff --git a/src/Services/InfoProviderSystem/PartInfoRetriever.php b/src/Services/InfoProviderSystem/PartInfoRetriever.php index 1a31b197d..0eb746429 100644 --- a/src/Services/InfoProviderSystem/PartInfoRetriever.php +++ b/src/Services/InfoProviderSystem/PartInfoRetriever.php @@ -27,6 +27,7 @@ use App\Services\InfoProviderSystem\DTOs\PartDetailDTO; use App\Services\InfoProviderSystem\DTOs\SearchResultDTO; use App\Services\InfoProviderSystem\Providers\InfoProviderInterface; +use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Contracts\Cache\CacheInterface; use Symfony\Contracts\Cache\ItemInterface; @@ -34,10 +35,12 @@ final class PartInfoRetriever { private const CACHE_DETAIL_EXPIRATION = 60 * 60 * 24 * 4; // 4 days - private const CACHE_RESULT_EXPIRATION = 60 * 60 * 24 * 7; // 7 days + private const CACHE_RESULT_EXPIRATION = 60 * 60 * 24 * 4; // 7 days public function __construct(private readonly ProviderRegistry $provider_registry, - private readonly DTOtoEntityConverter $dto_to_entity_converter, private readonly CacheInterface $partInfoCache) + private readonly DTOtoEntityConverter $dto_to_entity_converter, private readonly CacheInterface $partInfoCache, + #[Autowire(param: "kernel.debug")] + private readonly bool $debugMode = false) { } @@ -56,6 +59,11 @@ public function searchByKeyword(string $keyword, array $providers): array $provider = $this->provider_registry->getProviderByKey($provider); } + //Ensure that the provider is active + if (!$provider->isActive()) { + throw new \RuntimeException("The provider with key {$provider->getProviderKey()} is not active!"); + } + if (!$provider instanceof InfoProviderInterface) { throw new \InvalidArgumentException("The provider must be either a provider key or a provider instance!"); } @@ -77,7 +85,7 @@ protected function searchInProvider(InfoProviderInterface $provider, string $key $escaped_keyword = urlencode($keyword); return $this->partInfoCache->get("search_{$provider->getProviderKey()}_{$escaped_keyword}", function (ItemInterface $item) use ($provider, $keyword) { //Set the expiration time - $item->expiresAfter(self::CACHE_RESULT_EXPIRATION); + $item->expiresAfter(!$this->debugMode ? self::CACHE_RESULT_EXPIRATION : 1); return $provider->searchByKeyword($keyword); }); @@ -94,11 +102,16 @@ public function getDetails(string $provider_key, string $part_id): PartDetailDTO { $provider = $this->provider_registry->getProviderByKey($provider_key); + //Ensure that the provider is active + if (!$provider->isActive()) { + throw new \RuntimeException("The provider with key $provider_key is not active!"); + } + //Generate key and escape reserved characters from the provider id $escaped_part_id = urlencode($part_id); return $this->partInfoCache->get("details_{$provider_key}_{$escaped_part_id}", function (ItemInterface $item) use ($provider, $part_id) { //Set the expiration time - $item->expiresAfter(self::CACHE_DETAIL_EXPIRATION); + $item->expiresAfter(!$this->debugMode ? self::CACHE_DETAIL_EXPIRATION : 1); return $provider->getDetails($part_id); }); diff --git a/src/Services/InfoProviderSystem/Providers/BatchInfoProviderInterface.php b/src/Services/InfoProviderSystem/Providers/BatchInfoProviderInterface.php new file mode 100644 index 000000000..549f117a8 --- /dev/null +++ b/src/Services/InfoProviderSystem/Providers/BatchInfoProviderInterface.php @@ -0,0 +1,40 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Services\InfoProviderSystem\Providers; + +use App\Services\InfoProviderSystem\DTOs\SearchResultDTO; + +/** + * This interface marks a provider as a info provider which can provide information directly in batch operations + */ +interface BatchInfoProviderInterface extends InfoProviderInterface +{ + /** + * Search for multiple keywords in a single batch operation and return the results, ordered by the keywords. + * This allows for a more efficient search compared to running multiple single searches. + * @param string[] $keywords + * @return array An associative array where the key is the keyword and the value is the search results for that keyword + */ + public function searchByKeywordsBatch(array $keywords): array; +} diff --git a/src/Services/InfoProviderSystem/Providers/DigikeyProvider.php b/src/Services/InfoProviderSystem/Providers/DigikeyProvider.php index d8e93321c..423b5244d 100644 --- a/src/Services/InfoProviderSystem/Providers/DigikeyProvider.php +++ b/src/Services/InfoProviderSystem/Providers/DigikeyProvider.php @@ -24,6 +24,7 @@ namespace App\Services\InfoProviderSystem\Providers; use App\Entity\Parts\ManufacturingStatus; +use App\Exceptions\OAuthReconnectRequiredException; use App\Services\InfoProviderSystem\DTOs\FileDTO; use App\Services\InfoProviderSystem\DTOs\ParameterDTO; use App\Services\InfoProviderSystem\DTOs\PartDetailDTO; @@ -31,6 +32,7 @@ use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO; use App\Services\InfoProviderSystem\DTOs\SearchResultDTO; use App\Services\OAuth\OAuthTokenManager; +use App\Settings\InfoProviderSystem\DigikeySettings; use Symfony\Contracts\HttpClient\HttpClientInterface; class DigikeyProvider implements InfoProviderInterface @@ -55,17 +57,16 @@ class DigikeyProvider implements InfoProviderInterface ]; public function __construct(HttpClientInterface $httpClient, private readonly OAuthTokenManager $authTokenManager, - private readonly string $currency, private readonly string $clientId, - private readonly string $language, private readonly string $country) + private readonly DigikeySettings $settings,) { //Create the HTTP client with some default options $this->digikeyClient = $httpClient->withOptions([ "base_uri" => self::BASE_URI, "headers" => [ - "X-DIGIKEY-Client-Id" => $clientId, - "X-DIGIKEY-Locale-Site" => $this->country, - "X-DIGIKEY-Locale-Language" => $this->language, - "X-DIGIKEY-Locale-Currency" => $this->currency, + "X-DIGIKEY-Client-Id" => $this->settings->clientId, + "X-DIGIKEY-Locale-Site" => $this->settings->country, + "X-DIGIKEY-Locale-Language" => $this->settings->language, + "X-DIGIKEY-Locale-Currency" => $this->settings->currency, "X-DIGIKEY-Customer-Id" => 0, ] ]); @@ -78,7 +79,8 @@ public function getProviderInfo(): array 'description' => 'This provider uses the DigiKey API to search for parts.', 'url' => 'https://www.digikey.com/', 'oauth_app_name' => self::OAUTH_APP_NAME, - 'disabled_help' => 'Set the PROVIDER_DIGIKEY_CLIENT_ID and PROVIDER_DIGIKEY_SECRET env option and connect OAuth to enable.' + 'disabled_help' => 'Set the Client ID and Secret in provider settings and connect OAuth to enable.', + 'settings_class' => DigikeySettings::class, ]; } @@ -101,41 +103,57 @@ public function getProviderKey(): string public function isActive(): bool { //The client ID has to be set and a token has to be available (user clicked connect) - return $this->clientId !== '' && $this->authTokenManager->hasToken(self::OAUTH_APP_NAME); + return $this->settings->clientId !== null && $this->settings->clientId !== '' && $this->authTokenManager->hasToken(self::OAUTH_APP_NAME); } public function searchByKeyword(string $keyword): array { $request = [ 'Keywords' => $keyword, - 'RecordCount' => 50, - 'RecordStartPosition' => 0, - 'ExcludeMarketPlaceProducts' => 'true', + 'Limit' => 50, + 'Offset' => 0, + 'FilterOptionsRequest' => [ + 'MarketPlaceFilter' => 'ExcludeMarketPlace', + ], ]; - $response = $this->digikeyClient->request('POST', '/Search/v3/Products/Keyword', [ - 'json' => $request, - 'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME) - ]); + //$response = $this->digikeyClient->request('POST', '/Search/v3/Products/Keyword', [ + try { + $response = $this->digikeyClient->request('POST', '/products/v4/search/keyword', [ + 'json' => $request, + 'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME) + ]); + + $response_array = $response->toArray(); + } catch (\InvalidArgumentException $exception) { + //Check if the exception was caused by an invalid or expired token + if (str_contains($exception->getMessage(), 'access_token')) { + throw OAuthReconnectRequiredException::forProvider($this->getProviderKey()); + } + + throw $exception; + } - $response_array = $response->toArray(); $result = []; $products = $response_array['Products']; foreach ($products as $product) { - $result[] = new SearchResultDTO( - provider_key: $this->getProviderKey(), - provider_id: $product['DigiKeyPartNumber'], - name: $product['ManufacturerPartNumber'], - description: $product['DetailedDescription'] ?? $product['ProductDescription'], - category: $this->getCategoryString($product), - manufacturer: $product['Manufacturer']['Value'] ?? null, - mpn: $product['ManufacturerPartNumber'], - preview_image_url: $product['PrimaryPhoto'] ?? null, - manufacturing_status: $this->productStatusToManufacturingStatus($product['ProductStatus']), - provider_url: $product['ProductUrl'], - ); + foreach ($product['ProductVariations'] as $variation) { + $result[] = new SearchResultDTO( + provider_key: $this->getProviderKey(), + provider_id: $variation['DigiKeyProductNumber'], + name: $product['ManufacturerProductNumber'], + description: $product['Description']['DetailedDescription'] ?? $product['Description']['ProductDescription'], + category: $this->getCategoryString($product), + manufacturer: $product['Manufacturer']['Name'] ?? null, + mpn: $product['ManufacturerProductNumber'], + preview_image_url: $product['PhotoUrl'] ?? null, + manufacturing_status: $this->productStatusToManufacturingStatus($product['ProductStatus']['Id']), + provider_url: $product['ProductUrl'], + footprint: $variation['PackageType']['Name'], //Use the footprint field, to show the user the package type (Tape & Reel, etc., as digikey has many different package types) + ); + } } return $result; @@ -143,62 +161,88 @@ public function searchByKeyword(string $keyword): array public function getDetails(string $id): PartDetailDTO { - $response = $this->digikeyClient->request('GET', '/Search/v3/Products/' . urlencode($id), [ - 'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME) - ]); + try { + $response = $this->digikeyClient->request('GET', '/products/v4/search/' . urlencode($id) . '/productdetails', [ + 'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME) + ]); + } catch (\InvalidArgumentException $exception) { + //Check if the exception was caused by an invalid or expired token + if (str_contains($exception->getMessage(), 'access_token')) { + throw OAuthReconnectRequiredException::forProvider($this->getProviderKey()); + } - $product = $response->toArray(); + throw $exception; + } + + $response_array = $response->toArray(); + $product = $response_array['Product']; $footprint = null; $parameters = $this->parametersToDTOs($product['Parameters'] ?? [], $footprint); - $media = $this->mediaToDTOs($product['MediaLinks']); + $media = $this->mediaToDTOs($id); + + // Get the price_breaks of the selected variation + $price_breaks = []; + foreach ($product['ProductVariations'] as $variation) { + if ($variation['DigiKeyProductNumber'] == $id) { + $price_breaks = $variation['StandardPricing'] ?? []; + break; + } + } return new PartDetailDTO( provider_key: $this->getProviderKey(), - provider_id: $product['DigiKeyPartNumber'], - name: $product['ManufacturerPartNumber'], - description: $product['DetailedDescription'] ?? $product['ProductDescription'], + provider_id: $id, + name: $product['ManufacturerProductNumber'], + description: $product['Description']['DetailedDescription'] ?? $product['Description']['ProductDescription'], category: $this->getCategoryString($product), - manufacturer: $product['Manufacturer']['Value'] ?? null, - mpn: $product['ManufacturerPartNumber'], - preview_image_url: $product['PrimaryPhoto'] ?? null, - manufacturing_status: $this->productStatusToManufacturingStatus($product['ProductStatus']), + manufacturer: $product['Manufacturer']['Name'] ?? null, + mpn: $product['ManufacturerProductNumber'], + preview_image_url: $product['PhotoUrl'] ?? null, + manufacturing_status: $this->productStatusToManufacturingStatus($product['ProductStatus']['Id']), provider_url: $product['ProductUrl'], footprint: $footprint, datasheets: $media['datasheets'], images: $media['images'], parameters: $parameters, - vendor_infos: $this->pricingToDTOs($product['StandardPricing'] ?? [], $product['DigiKeyPartNumber'], $product['ProductUrl']), + vendor_infos: $this->pricingToDTOs($price_breaks, $id, $product['ProductUrl']), ); } /** * Converts the product status from the Digikey API to the manufacturing status used in Part-DB - * @param string|null $dk_status + * @param int|null $dk_status * @return ManufacturingStatus|null */ - private function productStatusToManufacturingStatus(?string $dk_status): ?ManufacturingStatus + private function productStatusToManufacturingStatus(?int $dk_status): ?ManufacturingStatus { + // The V4 can use strings to get the status, but if you have changed the PROVIDER_DIGIKEY_LANGUAGE it will not match. + // Using the Id instead which should be fixed. + // + // The API is not well documented and the ID are not there yet, so were extracted using "trial and error". + // The 'Preliminary' id was not found in several categories so I was unable to extract it. Disabled for now. return match ($dk_status) { null => null, - 'Active' => ManufacturingStatus::ACTIVE, - 'Obsolete' => ManufacturingStatus::DISCONTINUED, - 'Discontinued at Digi-Key', 'Last Time Buy' => ManufacturingStatus::EOL, - 'Not For New Designs' => ManufacturingStatus::NRFND, - 'Preliminary' => ManufacturingStatus::ANNOUNCED, + 0 => ManufacturingStatus::ACTIVE, + 1 => ManufacturingStatus::DISCONTINUED, + 2, 4 => ManufacturingStatus::EOL, + 7 => ManufacturingStatus::NRFND, + //'Preliminary' => ManufacturingStatus::ANNOUNCED, default => ManufacturingStatus::NOT_SET, }; } private function getCategoryString(array $product): string { - $category = $product['Category']['Value']; - $sub_category = $product['Family']['Value']; + $category = $product['Category']['Name']; + $sub_category = current($product['Category']['ChildCategories']); - //Replace the ' - ' category separator with ' -> ' - $sub_category = str_replace(' - ', ' -> ', $sub_category); + if ($sub_category) { + //Replace the ' - ' category separator with ' -> ' + $category = $category . ' -> ' . str_replace(' - ', ' -> ', $sub_category["Name"]); + } - return $category . ' -> ' . $sub_category; + return $category; } /** @@ -215,18 +259,18 @@ private function parametersToDTOs(array $parameters, string|null &$footprint_nam foreach ($parameters as $parameter) { if ($parameter['ParameterId'] === 1291) { //Meaning "Manufacturer given footprint" - $footprint_name = $parameter['Value']; + $footprint_name = $parameter['ValueText']; } - if (in_array(trim((string) $parameter['Value']), ['', '-'], true)) { + if (in_array(trim((string) $parameter['ValueText']), ['', '-'], true)) { continue; } //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']); + $results[] = new ParameterDTO(name: $parameter['ParameterText'], value_text: $parameter['ValueText']); } else { //Otherwise try to parse it as a numerical value - $results[] = ParameterDTO::parseValueIncludingUnit($parameter['Parameter'], $parameter['Value']); + $results[] = ParameterDTO::parseValueIncludingUnit($parameter['ParameterText'], $parameter['ValueText']); } } @@ -245,7 +289,7 @@ private function pricingToDTOs(array $price_breaks, string $order_number, string $prices = []; foreach ($price_breaks as $price_break) { - $prices[] = new PriceDTO(minimum_discount_amount: $price_break['BreakQuantity'], price: (string) $price_break['UnitPrice'], currency_iso_code: $this->currency); + $prices[] = new PriceDTO(minimum_discount_amount: $price_break['BreakQuantity'], price: (string) $price_break['UnitPrice'], currency_iso_code: $this->settings->currency); } return [ @@ -254,16 +298,22 @@ private function pricingToDTOs(array $price_breaks, string $order_number, string } /** - * @param array $media_links + * @param string $id The Digikey product number, to get the media for * @return FileDTO[][] * @phpstan-return array */ - private function mediaToDTOs(array $media_links): array + private function mediaToDTOs(string $id): array { $datasheets = []; $images = []; - foreach ($media_links as $media_link) { + $response = $this->digikeyClient->request('GET', '/products/v4/search/' . urlencode($id) . '/media', [ + 'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME) + ]); + + $media_array = $response->toArray(); + + foreach ($media_array['MediaLinks'] as $media_link) { $file = new FileDTO(url: $media_link['Url'], name: $media_link['Title']); switch ($media_link['MediaType']) { diff --git a/src/Services/InfoProviderSystem/Providers/Element14Provider.php b/src/Services/InfoProviderSystem/Providers/Element14Provider.php index ad70f9d9b..27dfb908a 100644 --- a/src/Services/InfoProviderSystem/Providers/Element14Provider.php +++ b/src/Services/InfoProviderSystem/Providers/Element14Provider.php @@ -29,13 +29,15 @@ use App\Services\InfoProviderSystem\DTOs\PartDetailDTO; use App\Services\InfoProviderSystem\DTOs\PriceDTO; use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO; +use App\Settings\InfoProviderSystem\Element14Settings; +use Composer\CaBundle\CaBundle; use Symfony\Contracts\HttpClient\HttpClientInterface; class Element14Provider implements InfoProviderInterface { private const ENDPOINT_URL = 'https://api.element14.com/catalog/products'; - private const API_VERSION_NUMBER = '1.2'; + private const API_VERSION_NUMBER = '1.4'; private const NUMBER_OF_RESULTS = 20; public const DISTRIBUTOR_NAME = 'Farnell'; @@ -43,9 +45,19 @@ class Element14Provider implements InfoProviderInterface private const COMPLIANCE_ATTRIBUTES = ['euEccn', 'hazardous', 'MSL', 'productTraceability', 'rohsCompliant', 'rohsPhthalatesCompliant', 'SVHC', 'tariffCode', 'usEccn', 'hazardCode']; - public function __construct(private readonly HttpClientInterface $element14Client, private readonly string $api_key, private readonly string $store_id) - { + private readonly HttpClientInterface $element14Client; + public function __construct(HttpClientInterface $element14Client, private readonly Element14Settings $settings) + { + /* We use the mozilla CA from the composer ca bundle directly, as some debian systems seems to have problems + * with the SSL.COM CA, element14 uses. See https://github.com/Part-DB/Part-DB-server/issues/866 + * + * This is a workaround until the issue is resolved in debian (or never). + * As this only affects this provider, this should have no negative impact and the CA bundle is still secure. + */ + $this->element14Client = $element14Client->withOptions([ + 'cafile' => CaBundle::getBundledCaBundlePath(), + ]); } public function getProviderInfo(): array @@ -54,7 +66,8 @@ public function getProviderInfo(): array 'name' => 'Farnell element14', 'description' => 'This provider uses the Farnell element14 API to search for parts.', 'url' => 'https://www.element14.com/', - 'disabled_help' => 'Configure the API key in the PROVIDER_ELEMENT14_KEY environment variable to enable.' + 'disabled_help' => 'Configure the API key in the provider settings to enable.', + 'settings_class' => Element14Settings::class, ]; } @@ -65,7 +78,7 @@ public function getProviderKey(): string public function isActive(): bool { - return $this->api_key !== ''; + return $this->settings->apiKey !== null && trim($this->settings->apiKey) !== ''; } /** @@ -77,13 +90,13 @@ private function queryByTerm(string $term): array $response = $this->element14Client->request('GET', self::ENDPOINT_URL, [ 'query' => [ 'term' => $term, - 'storeInfo.id' => $this->store_id, + 'storeInfo.id' => $this->settings->storeId, 'resultsSettings.offset' => 0, 'resultsSettings.numberOfResults' => self::NUMBER_OF_RESULTS, 'resultsSettings.responseGroup' => 'large', - 'callInfo.apiKey' => $this->api_key, + 'callInfo.apiKey' => $this->settings->apiKey, 'callInfo.responseDataFormat' => 'json', - 'callInfo.version' => self::API_VERSION_NUMBER, + 'versionNumber' => self::API_VERSION_NUMBER, ], ]); @@ -107,21 +120,18 @@ private function queryByTerm(string $term): array mpn: $product['translatedManufacturerPartNumber'], preview_image_url: $this->toImageUrl($product['image'] ?? null), manufacturing_status: $this->releaseStatusCodeToManufacturingStatus($product['releaseStatusCode'] ?? null), - provider_url: $this->generateProductURL($product['sku']), + provider_url: $product['productURL'], + notes: $product['productOverview']['description'] ?? null, datasheets: $this->parseDataSheets($product['datasheets'] ?? null), parameters: $this->attributesToParameters($product['attributes'] ?? null), - vendor_infos: $this->pricesToVendorInfo($product['sku'], $product['prices'] ?? []) + vendor_infos: $this->pricesToVendorInfo($product['sku'], $product['prices'] ?? [], $product['productURL']), + ); } return $result; } - private function generateProductURL($sku): string - { - return 'https://' . $this->store_id . '/' . $sku; - } - /** * @param array|null $datasheets * @return FileDTO[]|null Array of FileDTOs @@ -152,7 +162,7 @@ private function toImageUrl(?array $image): ?string $locale = 'en_US'; } - return 'https://' . $this->store_id . '/productimages/standard/' . $locale . $image['baseName']; + return 'https://' . $this->settings->storeId . '/productimages/standard/' . $locale . $image['baseName']; } /** @@ -161,7 +171,7 @@ private function toImageUrl(?array $image): ?string * @param array $prices * @return array */ - private function pricesToVendorInfo(string $sku, array $prices): array + private function pricesToVendorInfo(string $sku, array $prices, string $product_url): array { $price_dtos = []; @@ -179,7 +189,7 @@ private function pricesToVendorInfo(string $sku, array $prices): array distributor_name: self::DISTRIBUTOR_NAME, order_number: $sku, prices: $price_dtos, - product_url: $this->generateProductURL($sku) + product_url: $product_url ) ]; } @@ -187,7 +197,7 @@ private function pricesToVendorInfo(string $sku, array $prices): array public function getUsedCurrency(): string { //Decide based on the shop ID - return match ($this->store_id) { + return match ($this->settings->storeId) { 'bg.farnell.com', 'at.farnell.com', 'si.farnell.com', 'sk.farnell.com', 'ro.farnell.com', 'pt.farnell.com', 'nl.farnell.com', 'be.farnell.com', 'lv.farnell.com', 'lt.farnell.com', 'it.farnell.com', 'fr.farnell.com', 'fi.farnell.com', 'ee.farnell.com', 'es.farnell.com', 'ie.farnell.com', 'cpcireland.farnell.com', 'de.farnell.com' => 'EUR', 'cz.farnell.com' => 'CZK', 'dk.farnell.com' => 'DKK', @@ -214,7 +224,7 @@ public function getUsedCurrency(): string 'tw.element14.com' => 'TWD', 'kr.element14.com' => 'KRW', 'vn.element14.com' => 'VND', - default => throw new \RuntimeException('Unknown store ID: ' . $this->store_id) + default => throw new \RuntimeException('Unknown store ID: ' . $this->settings->storeId) }; } @@ -299,4 +309,4 @@ public function getCapabilities(): array ProviderCapabilities::DATASHEET, ]; } -} \ No newline at end of file +} diff --git a/src/Services/InfoProviderSystem/Providers/EmptyProvider.php b/src/Services/InfoProviderSystem/Providers/EmptyProvider.php new file mode 100644 index 000000000..e0de9772b --- /dev/null +++ b/src/Services/InfoProviderSystem/Providers/EmptyProvider.php @@ -0,0 +1,76 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Services\InfoProviderSystem\Providers; + +use App\Services\InfoProviderSystem\DTOs\FileDTO; +use App\Services\InfoProviderSystem\DTOs\PartDetailDTO; +use App\Services\InfoProviderSystem\DTOs\SearchResultDTO; +use Symfony\Component\DependencyInjection\Attribute\When; + +/** + * This is a provider, which is used during tests. It always returns no results. + */ +#[When(env: 'test')] +class EmptyProvider implements InfoProviderInterface +{ + public function getProviderInfo(): array + { + return [ + 'name' => 'Empty Provider', + 'description' => 'This is a test provider', + //'url' => 'https://example.com/', + 'disabled_help' => 'This provider is disabled for testing purposes' + ]; + } + + public function getProviderKey(): string + { + return 'empty'; + } + + public function isActive(): bool + { + return true; + } + + public function searchByKeyword(string $keyword): array + { + return [ + + ]; + } + + public function getCapabilities(): array + { + return [ + ProviderCapabilities::BASIC, + ProviderCapabilities::FOOTPRINT, + ]; + } + + public function getDetails(string $id): PartDetailDTO + { + throw new \RuntimeException('No part details available'); + } +} diff --git a/src/Services/InfoProviderSystem/Providers/InfoProviderInterface.php b/src/Services/InfoProviderSystem/Providers/InfoProviderInterface.php index 30821bad7..1f787559b 100644 --- a/src/Services/InfoProviderSystem/Providers/InfoProviderInterface.php +++ b/src/Services/InfoProviderSystem/Providers/InfoProviderInterface.php @@ -39,8 +39,9 @@ interface InfoProviderInterface * - 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 + * - settings_class?: The class name of the settings class which contains the settings for this provider (e.g. "App\Settings\InfoProviderSettings\DigikeySettings"). If this is set a link to the settings will be shown * - * @phpstan-return array{ name: string, description?: string, logo?: string, url?: string, disabled_help?: string, oauth_app_name?: string } + * @phpstan-return array{ name: string, description?: string, logo?: string, url?: string, disabled_help?: string, oauth_app_name?: string, settings_class?: class-string } */ public function getProviderInfo(): array; @@ -78,4 +79,4 @@ public function getDetails(string $id): PartDetailDTO; * @return ProviderCapabilities[] */ public function getCapabilities(): array; -} \ No newline at end of file +} diff --git a/src/Services/InfoProviderSystem/Providers/LCSCProvider.php b/src/Services/InfoProviderSystem/Providers/LCSCProvider.php index d903a8ddd..ede34eb86 100755 --- a/src/Services/InfoProviderSystem/Providers/LCSCProvider.php +++ b/src/Services/InfoProviderSystem/Providers/LCSCProvider.php @@ -29,17 +29,18 @@ use App\Services\InfoProviderSystem\DTOs\PartDetailDTO; use App\Services\InfoProviderSystem\DTOs\PriceDTO; use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO; +use App\Settings\InfoProviderSystem\LCSCSettings; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Contracts\HttpClient\HttpClientInterface; -class LCSCProvider implements InfoProviderInterface +class LCSCProvider implements BatchInfoProviderInterface { private const ENDPOINT_URL = 'https://wmsc.lcsc.com/ftps/wm'; public const DISTRIBUTOR_NAME = 'LCSC'; - public function __construct(private readonly HttpClientInterface $lcscClient, private readonly string $currency, private readonly bool $enabled = true) + public function __construct(private readonly HttpClientInterface $lcscClient, private readonly LCSCSettings $settings) { } @@ -50,7 +51,8 @@ public function getProviderInfo(): array 'name' => 'LCSC', 'description' => 'This provider uses the (unofficial) LCSC API to search for parts.', 'url' => 'https://www.lcsc.com/', - 'disabled_help' => 'Set PROVIDER_LCSC_ENABLED to 1 (or true) in your environment variable config.' + 'disabled_help' => 'Enable this provider in the provider settings.', + 'settings_class' => LCSCSettings::class, ]; } @@ -62,18 +64,19 @@ public function getProviderKey(): string // This provider is always active public function isActive(): bool { - return $this->enabled; + return $this->settings->enabled; } /** * @param string $id + * @param bool $lightweight If true, skip expensive operations like datasheet resolution * @return PartDetailDTO */ - private function queryDetail(string $id): PartDetailDTO + private function queryDetail(string $id, bool $lightweight = false): PartDetailDTO { $response = $this->lcscClient->request('GET', self::ENDPOINT_URL . "/product/detail", [ 'headers' => [ - 'Cookie' => new Cookie('currencyCode', $this->currency) + 'Cookie' => new Cookie('currencyCode', $this->settings->currency) ], 'query' => [ 'productCode' => $id, @@ -87,7 +90,7 @@ private function queryDetail(string $id): PartDetailDTO throw new \RuntimeException('Could not find product code: ' . $id); } - return $this->getPartDetail($product); + return $this->getPartDetail($product, $lightweight); } /** @@ -97,35 +100,47 @@ private function queryDetail(string $id): PartDetailDTO private function getRealDatasheetUrl(?string $url): string { if ($url !== null && trim($url) !== '' && preg_match("/^https:\/\/(datasheet\.lcsc\.com|www\.lcsc\.com\/datasheet)\/.*(C\d+)\.pdf$/", $url, $matches) > 0) { - if (preg_match("/^https:\/\/datasheet\.lcsc\.com\/lcsc\/(.*\.pdf)$/", $url, $rewriteMatches) > 0) { - $url = 'https://www.lcsc.com/datasheet/lcsc_datasheet_' . $rewriteMatches[1]; - } - $response = $this->lcscClient->request('GET', $url, [ - 'headers' => [ - 'Referer' => 'https://www.lcsc.com/product-detail/_' . $matches[2] . '.html' - ], - ]); - if (preg_match('/(previewPdfUrl): ?("[^"]+wmsc\.lcsc\.com[^"]+\.pdf")/', $response->getContent(), $matches) > 0) { - //HACKY: The URL string contains escaped characters like \u002F, etc. To decode it, the JSON decoding is reused - //See https://github.com/Part-DB/Part-DB-server/pull/582#issuecomment-2033125934 - $jsonObj = json_decode('{"' . $matches[1] . '": ' . $matches[2] . '}'); - $url = $jsonObj->previewPdfUrl; - } + if (preg_match("/^https:\/\/datasheet\.lcsc\.com\/lcsc\/(.*\.pdf)$/", $url, $rewriteMatches) > 0) { + $url = 'https://www.lcsc.com/datasheet/lcsc_datasheet_' . $rewriteMatches[1]; + } + $response = $this->lcscClient->request('GET', $url, [ + 'headers' => [ + 'Referer' => 'https://www.lcsc.com/product-detail/_' . $matches[2] . '.html' + ], + ]); + if (preg_match('/(previewPdfUrl): ?("[^"]+wmsc\.lcsc\.com[^"]+\.pdf")/', $response->getContent(), $matches) > 0) { + //HACKY: The URL string contains escaped characters like \u002F, etc. To decode it, the JSON decoding is reused + //See https://github.com/Part-DB/Part-DB-server/pull/582#issuecomment-2033125934 + $jsonObj = json_decode('{"' . $matches[1] . '": ' . $matches[2] . '}'); + $url = $jsonObj->previewPdfUrl; + } } return $url; } /** * @param string $term + * @param bool $lightweight If true, skip expensive operations like datasheet resolution * @return PartDetailDTO[] */ - private function queryByTerm(string $term): array + private function queryByTerm(string $term, bool $lightweight = false): array { - $response = $this->lcscClient->request('GET', self::ENDPOINT_URL . "/search/global", [ + // Optimize: If term looks like an LCSC part number (starts with C followed by digits), + // use direct detail query instead of slower search + if (preg_match('/^C\d+$/i', trim($term))) { + try { + return [$this->queryDetail(trim($term), $lightweight)]; + } catch (\Exception $e) { + // If direct lookup fails, fall back to search + // This handles cases where the C-code might not exist + } + } + + $response = $this->lcscClient->request('POST', self::ENDPOINT_URL . "/search/v2/global", [ 'headers' => [ - 'Cookie' => new Cookie('currencyCode', $this->currency) + 'Cookie' => new Cookie('currencyCode', $this->settings->currency) ], - 'query' => [ + 'json' => [ 'keyword' => $term, ], ]); @@ -143,11 +158,11 @@ private function queryByTerm(string $term): array // detailed product listing. It does so utilizing a product tip field. // If product tip exists and there are no products in the product list try a detail query if (count($products) === 0 && $tipProductCode !== null) { - $result[] = $this->queryDetail($tipProductCode); + $result[] = $this->queryDetail($tipProductCode, $lightweight); } foreach ($products as $product) { - $result[] = $this->getPartDetail($product); + $result[] = $this->getPartDetail($product, $lightweight); } return $result; @@ -163,6 +178,9 @@ private function sanitizeField(?string $field): ?string if ($field === null) { return null; } + // Replace "range" indicators with mathematical tilde symbols + // so they don't get rendered as strikethrough by Markdown + $field = preg_replace("/~/", "\u{223c}", $field); return strip_tags($field); } @@ -173,7 +191,7 @@ private function sanitizeField(?string $field): ?string * @param array $product * @return PartDetailDTO */ - private function getPartDetail(array $product): PartDetailDTO + private function getPartDetail(array $product, bool $lightweight = false): PartDetailDTO { // Get product images in advance $product_images = $this->getProductImages($product['productImages'] ?? null); @@ -195,9 +213,6 @@ private function getPartDetail(array $product): PartDetailDTO $category = $product['parentCatalogName'] ?? null; if (isset($product['catalogName'])) { $category = ($category ?? '') . ' -> ' . $product['catalogName']; - - // Replace the / with a -> for better readability - $category = str_replace('/', ' -> ', $category); } return new PartDetailDTO( @@ -212,10 +227,10 @@ private function getPartDetail(array $product): PartDetailDTO manufacturing_status: null, provider_url: $this->getProductShortURL($product['productCode']), footprint: $this->sanitizeField($footprint), - datasheets: $this->getProductDatasheets($product['pdfUrl'] ?? null), - images: $product_images, - parameters: $this->attributesToParameters($product['paramVOList'] ?? []), - vendor_infos: $this->pricesToVendorInfo($product['productCode'], $this->getProductShortURL($product['productCode']), $product['productPriceList'] ?? []), + datasheets: $lightweight ? [] : $this->getProductDatasheets($product['pdfUrl'] ?? null), + images: $product_images, // Always include images - users need to see them + parameters: $lightweight ? [] : $this->attributesToParameters($product['paramVOList'] ?? []), + vendor_infos: $lightweight ? [] : $this->pricesToVendorInfo($product['productCode'], $this->getProductShortURL($product['productCode']), $product['productPriceList'] ?? []), mass: $product['weight'] ?? null, ); } @@ -273,7 +288,7 @@ private function getUsedCurrency(string $currency): string 'kr.' => 'DKK', 'โ‚น' => 'INR', //Fallback to the configured currency - default => $this->currency, + default => $this->settings->currency, }; } @@ -284,7 +299,7 @@ private function getUsedCurrency(string $currency): string */ private function getProductShortURL(string $product_code): string { - return 'https://www.lcsc.com/product-detail/' . $product_code .'.html'; + return 'https://www.lcsc.com/product-detail/' . $product_code . '.html'; } /** @@ -325,7 +340,7 @@ private function attributesToParameters(?array $attributes): array //Skip this attribute if it's empty if (in_array(trim((string) $attribute['paramValueEn']), ['', '-'], true)) { - continue; + continue; } $result[] = ParameterDTO::parseValueIncludingUnit(name: $attribute['paramNameEn'], value: $attribute['paramValueEn'], group: null); @@ -336,12 +351,86 @@ private function attributesToParameters(?array $attributes): array public function searchByKeyword(string $keyword): array { - return $this->queryByTerm($keyword); + return $this->queryByTerm($keyword, true); // Use lightweight mode for search + } + + /** + * Batch search multiple keywords asynchronously (like JavaScript Promise.all) + * @param array $keywords Array of keywords to search + * @return array Results indexed by keyword + */ + public function searchByKeywordsBatch(array $keywords): array + { + if (empty($keywords)) { + return []; + } + + $responses = []; + $results = []; + + // Start all requests immediately (like JavaScript promises without await) + foreach ($keywords as $keyword) { + if (preg_match('/^C\d+$/i', trim($keyword))) { + // Direct detail API call for C-codes + $responses[$keyword] = $this->lcscClient->request('GET', self::ENDPOINT_URL . "/product/detail", [ + 'headers' => [ + 'Cookie' => new Cookie('currencyCode', $this->settings->currency) + ], + 'query' => [ + 'productCode' => trim($keyword), + ], + ]); + } else { + // Search API call for other terms + $responses[$keyword] = $this->lcscClient->request('POST', self::ENDPOINT_URL . "/search/v2/global", [ + 'headers' => [ + 'Cookie' => new Cookie('currencyCode', $this->settings->currency) + ], + 'json' => [ + 'keyword' => $keyword, + ], + ]); + } + } + + // Now collect all results (like .then() in JavaScript) + foreach ($responses as $keyword => $response) { + try { + $arr = $response->toArray(); // This waits for the response + $results[$keyword] = $this->processSearchResponse($arr, $keyword); + } catch (\Exception $e) { + $results[$keyword] = []; // Empty results on error + } + } + + return $results; + } + + private function processSearchResponse(array $arr, string $keyword): array + { + $result = []; + + // Check if this looks like a detail response (direct C-code lookup) + if (isset($arr['result']['productCode'])) { + $product = $arr['result']; + $result[] = $this->getPartDetail($product, true); // lightweight mode + } else { + // This is a search response + $products = $arr['result']['productSearchResultVO']['productList'] ?? []; + $tipProductCode = $arr['result']['tipProductDetailUrlVO']['productCode'] ?? null; + + // If no products but has tip, we'd need another API call - skip for batch mode + foreach ($products as $product) { + $result[] = $this->getPartDetail($product, true); // lightweight mode + } + } + + return $result; } public function getDetails(string $id): PartDetailDTO { - $tmp = $this->queryByTerm($id); + $tmp = $this->queryByTerm($id, false); if (count($tmp) === 0) { throw new \RuntimeException('No part found with ID ' . $id); } diff --git a/src/Services/InfoProviderSystem/Providers/MouserProvider.php b/src/Services/InfoProviderSystem/Providers/MouserProvider.php index c36fab66f..3171c9947 100644 --- a/src/Services/InfoProviderSystem/Providers/MouserProvider.php +++ b/src/Services/InfoProviderSystem/Providers/MouserProvider.php @@ -37,6 +37,7 @@ use App\Services\InfoProviderSystem\DTOs\PartDetailDTO; use App\Services\InfoProviderSystem\DTOs\PriceDTO; use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO; +use App\Settings\InfoProviderSystem\MouserSettings; use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\ResponseInterface; @@ -50,10 +51,7 @@ class MouserProvider implements InfoProviderInterface public function __construct( private readonly HttpClientInterface $mouserClient, - private readonly string $api_key, - private readonly string $language, - private readonly string $options, - private readonly int $search_limit + private readonly MouserSettings $settings, ) { } @@ -63,7 +61,8 @@ public function getProviderInfo(): array 'name' => 'Mouser', 'description' => 'This provider uses the Mouser API to search for parts.', 'url' => 'https://www.mouser.com/', - 'disabled_help' => 'Configure the API key in the PROVIDER_MOUSER_KEY environment variable to enable.' + 'disabled_help' => 'Configure the API key in the provider settings to enable.', + 'settings_class' => MouserSettings::class ]; } @@ -74,7 +73,7 @@ public function getProviderKey(): string public function isActive(): bool { - return $this->api_key !== ''; + return $this->settings->apiKey !== '' && $this->settings->apiKey !== null; } public function searchByKeyword(string $keyword): array @@ -94,6 +93,7 @@ public function searchByKeyword(string $keyword): array From the startingRecord, the number of records specified will be returned up to the end of the recordset. This is useful for paging through the complete recordset of parts matching keyword. + searchOptions string Optional. If not provided, the default is None. @@ -119,19 +119,28 @@ public function searchByKeyword(string $keyword): array $response = $this->mouserClient->request('POST', self::ENDPOINT_URL."/keyword", [ 'query' => [ - 'apiKey' => $this->api_key, + 'apiKey' => $this->settings->apiKey ], 'json' => [ 'SearchByKeywordRequest' => [ 'keyword' => $keyword, - 'records' => $this->search_limit, //self::NUMBER_OF_RESULTS, + 'records' => $this->settings->searchLimit, //self::NUMBER_OF_RESULTS, 'startingRecord' => 0, - 'searchOptions' => $this->options, - 'searchWithYourSignUpLanguage' => $this->language, + 'searchOptions' => $this->settings->searchOption->value, + 'searchWithYourSignUpLanguage' => $this->settings->searchWithSignUpLanguage ? 'true' : 'false', ] ], ]); + // Check for API errors before processing response + if ($response->getStatusCode() !== 200) { + throw new \RuntimeException(sprintf( + 'Mouser API returned HTTP %d: %s', + $response->getStatusCode(), + $response->getContent(false) + )); + } + return $this->responseToDTOArray($response); } @@ -160,7 +169,7 @@ public function getDetails(string $id): PartDetailDTO $response = $this->mouserClient->request('POST', self::ENDPOINT_URL."/partnumber", [ 'query' => [ - 'apiKey' => $this->api_key, + 'apiKey' => $this->settings->apiKey, ], 'json' => [ 'SearchByPartRequest' => [ @@ -169,6 +178,16 @@ public function getDetails(string $id): PartDetailDTO ] ], ]); + + // Check for API errors before processing response + if ($response->getStatusCode() !== 200) { + throw new \RuntimeException(sprintf( + 'Mouser API returned HTTP %d: %s', + $response->getStatusCode(), + $response->getContent(false) + )); + } + $tmp = $this->responseToDTOArray($response); //Ensure that we have exactly one result @@ -176,11 +195,16 @@ public function getDetails(string $id): PartDetailDTO throw new \RuntimeException('No part found with ID '.$id); } + //Manually filter out the part with the correct ID + $tmp = array_filter($tmp, fn(PartDetailDTO $part) => $part->provider_id === $id); + if (count($tmp) === 0) { + throw new \RuntimeException('No part found with ID '.$id); + } if (count($tmp) > 1) { - throw new \RuntimeException('Multiple parts found with ID '.$id . ' ('.count($tmp).' found). This is basically a bug in Mousers API response. See issue #616.'); + throw new \RuntimeException('Multiple parts found with ID '.$id); } - return $tmp[0]; + return reset($tmp); } public function getCapabilities(): array @@ -281,6 +305,17 @@ private function priceStrToFloat($val): float return (float)$val; } + private function mapCurrencyCode(string $currency): string + { + //Mouser uses "RMB" for Chinese Yuan, but the correct ISO code is "CNY" + if ($currency === "RMB") { + return "CNY"; + } + + //For all other currencies, we assume that the ISO code is correct + return $currency; + } + /** * Converts the pricing (StandardPricing field) from the Mouser API to an array of PurchaseInfoDTOs * @param array $price_breaks @@ -297,7 +332,7 @@ private function pricingToDTOs(array $price_breaks, string $order_number, string $prices[] = new PriceDTO( minimum_discount_amount: $price_break['Quantity'], price: (string)$number, - currency_iso_code: $price_break['Currency'] + currency_iso_code: $this->mapCurrencyCode($price_break['Currency']) ); } @@ -341,4 +376,4 @@ private function releaseStatusCodeToManufacturingStatus(?string $productStatus, return $tmp; } -} \ No newline at end of file +} diff --git a/src/Services/InfoProviderSystem/Providers/OEMSecretsProvider.php b/src/Services/InfoProviderSystem/Providers/OEMSecretsProvider.php index 57c5b8153..b705e04a2 100644 --- a/src/Services/InfoProviderSystem/Providers/OEMSecretsProvider.php +++ b/src/Services/InfoProviderSystem/Providers/OEMSecretsProvider.php @@ -88,6 +88,8 @@ use App\Services\InfoProviderSystem\DTOs\PriceDTO; use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO; use App\Services\InfoProviderSystem\DTOs\ParameterDTO; +use App\Settings\InfoProviderSystem\OEMSecretsSettings; +use App\Settings\InfoProviderSystem\OEMSecretsSortMode; use Symfony\Contracts\HttpClient\HttpClientInterface; use Psr\Cache\CacheItemPoolInterface; @@ -99,12 +101,7 @@ class OEMSecretsProvider implements InfoProviderInterface 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 OEMSecretsSettings $settings, private readonly CacheItemPoolInterface $partInfoCache ) { @@ -249,7 +246,8 @@ public function getProviderInfo(): array '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.' + 'disabled_help' => 'Configure the API key in the provider settings to enable.', + 'settings_class' => OEMSecretsSettings::class ]; } /** @@ -268,7 +266,7 @@ public function getProviderKey(): string */ public function isActive(): bool { - return $this->api_key !== ''; + return $this->settings->apiKey !== null && $this->settings->apiKey !== ''; } @@ -288,18 +286,18 @@ public function isActive(): bool public function searchByKeyword(string $keyword): array { /* - oemsecrets Part Search API 3.0.1 + oemsecrets Part Search API 3.0.1 "https://oemsecretsapi.com/partsearch?%20%20%20%20%20%20%20%20%20searchTerm=BC547%20%20%20%20%20%20%20%20%20&apiKey=icawpb0bspoo2c6s64uv4vpdfp2vgr7e27bxw0yct2bzh87mpl027x353uelpq2x%20%20%20%20%20%20%20%20%20¤cy=EUR-%20%20%20%20%20%20%20%20&countryCode=IT" - + &countryCode=IT" + partsearch description: - Use the Part Search API to find distributor data for a full or partial manufacturer + 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 @@ -307,14 +305,14 @@ public function searchByKeyword(string $keyword): array 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 - + */ @@ -324,9 +322,9 @@ public function searchByKeyword(string $keyword): array $response = $this->oemsecretsClient->request('GET', self::ENDPOINT_URL, [ 'query' => [ 'searchTerm' => $keyword, - 'apiKey' => $this->api_key, - 'currency' => $this->currency, - 'countryCode' => $this->country_code, + 'apiKey' => $this->settings->apiKey, + 'currency' => $this->settings->currency, + 'countryCode' => $this->settings->country, ], ]); @@ -533,7 +531,7 @@ private function processBatch( // Extract prices $priceDTOs = $this->getPrices($product); - if (empty($priceDTOs) && (int)$this->zero_price === 0) { + if (empty($priceDTOs) && !$this->settings->keepZeroPrices) { return null; // Skip products without valid prices } @@ -557,7 +555,7 @@ private function processBatch( } $imagesResults[$provider_id] = $this->getImages($product, $imagesResults[$provider_id] ?? []); - if ($this->set_param == 1) { + if ($this->settings->parseParams) { $parametersResults[$provider_id] = $this->getParameters($product, $parametersResults[$provider_id] ?? []); } else { $parametersResults[$provider_id] = []; @@ -582,7 +580,7 @@ private function processBatch( $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] ?? ''; + $regionForEnvCountry = $this->countryCodeToRegionMap[$this->settings->country] ?? ''; // Convert to string before comparison to avoid mixed types $countryCodeA = (string) $countryCodeA; @@ -599,9 +597,9 @@ private function processBatch( } // Step 1: country_code from the environment - if ($countryCodeA === $this->country_code && $countryCodeB !== $this->country_code) { + if ($countryCodeA === $this->settings->country && $countryCodeB !== $this->settings->country) { return -1; - } elseif ($countryCodeA !== $this->country_code && $countryCodeB === $this->country_code) { + } elseif ($countryCodeA !== $this->settings->country && $countryCodeB === $this->settings->country) { return 1; } @@ -681,8 +679,8 @@ private function getPrices(array $product): array 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]; + if (isset($prices[$this->settings->currency]) && is_array($prices[$this->settings->currency])) { + $priceDetails = $prices[$this->$this->settings->currency]; foreach ($priceDetails as $priceDetail) { if ( is_array($priceDetail) && @@ -694,7 +692,7 @@ private function getPrices(array $product): array $priceDTOs[] = new PriceDTO( minimum_discount_amount: (float)$priceDetail['unit_break'], price: (string)$priceDetail['unit_price'], - currency_iso_code: $this->currency, + currency_iso_code: $this->settings->currency, includes_tax: false, price_related_quantity: 1.0 ); @@ -1221,7 +1219,7 @@ private function customParseValueIncludingUnit(string $name, string $value): arr * - '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 + private function customSplitIntoValueAndUnit(string $value1, ?string $value2 = null): array { // Separate numbers and units (basic parsing handling) $unit = null; @@ -1293,7 +1291,7 @@ private function generateInquiryUrl(string $partNumber, string $oemInquiry = 'co 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') { + if ($this->settings->sortMode !== OEMSecretsSortMode::COMPLETENESS && $this->settings->sortMode !== OEMSecretsSortMode::MANUFACTURER) { return; } usort($resultsData, function ($a, $b) use ($searchKeyword) { @@ -1332,9 +1330,9 @@ private function sortResultsData(array &$resultsData, string $searchKeyword): vo } // Final sorting: by completeness or manufacturer, if necessary - if ($this->sort_criteria === 'C') { + if ($this->settings->sortMode === OEMSecretsSortMode::COMPLETENESS) { return $this->compareByCompleteness($a, $b); - } elseif ($this->sort_criteria === 'M') { + } elseif ($this->settings->sortMode === OEMSecretsSortMode::MANUFACTURER) { return strcasecmp($a->manufacturer, $b->manufacturer); } @@ -1468,4 +1466,4 @@ private function unwrapURL(?string $url): ?string 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 e28162ba2..1142f4ef8 100644 --- a/src/Services/InfoProviderSystem/Providers/OctopartProvider.php +++ b/src/Services/InfoProviderSystem/Providers/OctopartProvider.php @@ -30,6 +30,7 @@ use App\Services\InfoProviderSystem\DTOs\PriceDTO; use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO; use App\Services\OAuth\OAuthTokenManager; +use App\Settings\InfoProviderSystem\OctopartSettings; use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\HttpClient\HttpOptions; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -114,9 +115,8 @@ class OctopartProvider implements InfoProviderInterface public function __construct(private readonly HttpClientInterface $httpClient, private readonly OAuthTokenManager $authTokenManager, private readonly CacheItemPoolInterface $partInfoCache, - private readonly string $clientId, private readonly string $secret, - private readonly string $currency, private readonly string $country, - private readonly int $search_limit, private readonly bool $onlyAuthorizedSellers) + private readonly OctopartSettings $settings, + ) { } @@ -170,7 +170,8 @@ public function getProviderInfo(): array 'name' => 'Octopart', 'description' => 'This provider uses the Nexar/Octopart API to search for parts on Octopart.', 'url' => 'https://www.octopart.com/', - 'disabled_help' => 'Set the PROVIDER_OCTOPART_CLIENT_ID and PROVIDER_OCTOPART_SECRET env option.' + 'disabled_help' => 'Set the Client ID and Secret in provider settings.', + 'settings_class' => OctopartSettings::class ]; } @@ -183,7 +184,8 @@ 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->secret !== ''; + return $this->settings->clientId !== null && $this->settings->clientId !== '' + && $this->settings->secret !== null && $this->settings->secret !== ''; } private function mapLifeCycleStatus(?string $value): ?ManufacturingStatus @@ -337,7 +339,7 @@ public function searchByKeyword(string $keyword): array ) { hits results { - part + part %s } } @@ -347,10 +349,10 @@ public function searchByKeyword(string $keyword): array $result = $this->makeGraphQLCall($graphQL, [ 'keyword' => $keyword, - 'limit' => $this->search_limit, - 'currency' => $this->currency, - 'country' => $this->country, - 'authorizedOnly' => $this->onlyAuthorizedSellers, + 'limit' => $this->settings->searchLimit, + 'currency' => $this->settings->currency, + 'country' => $this->settings->country, + 'authorizedOnly' => $this->settings->onlyAuthorizedSellers, ]); $tmp = []; @@ -383,9 +385,9 @@ public function getDetails(string $id): PartDetailDTO $result = $this->makeGraphQLCall($graphql, [ 'ids' => [$id], - 'currency' => $this->currency, - 'country' => $this->country, - 'authorizedOnly' => $this->onlyAuthorizedSellers, + 'currency' => $this->settings->currency, + 'country' => $this->settings->country, + 'authorizedOnly' => $this->settings->onlyAuthorizedSellers, ]); $tmp = $this->partResultToDTO($result['data']['supParts'][0]); @@ -403,4 +405,4 @@ public function getCapabilities(): array ProviderCapabilities::PRICE, ]; } -} \ No newline at end of file +} diff --git a/src/Services/InfoProviderSystem/Providers/PollinProvider.php b/src/Services/InfoProviderSystem/Providers/PollinProvider.php new file mode 100644 index 000000000..b74e0365d --- /dev/null +++ b/src/Services/InfoProviderSystem/Providers/PollinProvider.php @@ -0,0 +1,251 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Services\InfoProviderSystem\Providers; + +use App\Entity\Parts\ManufacturingStatus; +use App\Entity\Parts\Part; +use App\Services\InfoProviderSystem\DTOs\FileDTO; +use App\Services\InfoProviderSystem\DTOs\ParameterDTO; +use App\Services\InfoProviderSystem\DTOs\PartDetailDTO; +use App\Services\InfoProviderSystem\DTOs\PriceDTO; +use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO; +use App\Services\InfoProviderSystem\DTOs\SearchResultDTO; +use App\Settings\InfoProviderSystem\PollinSettings; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\DomCrawler\Crawler; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +class PollinProvider implements InfoProviderInterface +{ + + public function __construct(private readonly HttpClientInterface $client, + private readonly PollinSettings $settings, + ) + { + } + + public function getProviderInfo(): array + { + return [ + 'name' => 'Pollin', + 'description' => 'Webscraping from pollin.de to get part information', + 'url' => 'https://www.pollin.de/', + 'disabled_help' => 'Enable the provider in provider settings', + 'settings_class' => PollinSettings::class, + ]; + } + + public function getProviderKey(): string + { + return 'pollin'; + } + + public function isActive(): bool + { + return $this->settings->enabled; + } + + public function searchByKeyword(string $keyword): array + { + $response = $this->client->request('GET', 'https://www.pollin.de/search', [ + 'query' => [ + 'search' => $keyword + ] + ]); + + $content = $response->getContent(); + + //If the response has us redirected to the product page, then just return the single item + if ($response->getInfo('redirect_count') > 0) { + return [$this->parseProductPage($content)]; + } + + $dom = new Crawler($content); + + $results = []; + + //Iterate over each div.product-box + $dom->filter('div.product-box')->each(function (Crawler $node) use (&$results) { + $results[] = new SearchResultDTO( + provider_key: $this->getProviderKey(), + provider_id: $node->filter('meta[itemprop="productID"]')->attr('content'), + name: $node->filter('a.product-name')->text(), + description: '', + preview_image_url: $node->filter('img.product-image')->attr('src'), + manufacturing_status: $this->mapAvailability($node->filter('link[itemprop="availability"]')->attr('href')), + provider_url: $node->filter('a.product-name')->attr('href') + ); + }); + + return $results; + } + + private function mapAvailability(string $availabilityURI): ManufacturingStatus + { + return match( $availabilityURI) { + 'http://schema.org/InStock' => ManufacturingStatus::ACTIVE, + 'http://schema.org/OutOfStock' => ManufacturingStatus::DISCONTINUED, + default => ManufacturingStatus::NOT_SET + }; + } + + public function getDetails(string $id): PartDetailDTO + { + //Ensure that $id is numeric + if (!is_numeric($id)) { + throw new \InvalidArgumentException("The id must be numeric!"); + } + + $response = $this->client->request('GET', 'https://www.pollin.de/search', [ + 'query' => [ + 'search' => $id + ] + ]); + + //The response must have us redirected to the product page + if ($response->getInfo('redirect_count') > 0) { + throw new \RuntimeException("Could not resolve the product page for the given id!"); + } + + $content = $response->getContent(); + + return $this->parseProductPage($content); + } + + private function parseProductPage(string $content): PartDetailDTO + { + $dom = new Crawler($content); + + $productPageUrl = $dom->filter('meta[property="product:product_link"]')->attr('content'); + $orderId = trim($dom->filter('span[itemprop="sku"]')->text()); //Text is important here + + //Calculate the mass + $massStr = $dom->filter('meta[itemprop="weight"]')->attr('content'); + //Remove the unit + $massStr = str_replace('kg', '', $massStr); + //Convert to float and convert to grams + $mass = (float) $massStr * 1000; + + //Parse purchase info + $purchaseInfo = new PurchaseInfoDTO('Pollin', $orderId, $this->parsePrices($dom), $productPageUrl); + + return new PartDetailDTO( + provider_key: $this->getProviderKey(), + provider_id: $orderId, + name: trim($dom->filter('meta[property="og:title"]')->attr('content')), + description: $dom->filter('meta[property="og:description"]')->attr('content'), + category: $this->parseCategory($dom), + manufacturer: $dom->filter('meta[property="product:brand"]')->count() > 0 ? $dom->filter('meta[property="product:brand"]')->attr('content') : null, + preview_image_url: $dom->filter('meta[property="og:image"]')->attr('content'), + //TODO: Find another way to determine the manufacturing status, as the itemprop="availability" is often is not existing anymore in the page + //manufacturing_status: $this->mapAvailability($dom->filter('link[itemprop="availability"]')->attr('href')), + provider_url: $productPageUrl, + notes: $this->parseNotes($dom), + datasheets: $this->parseDatasheets($dom), + parameters: $this->parseParameters($dom), + vendor_infos: [$purchaseInfo], + mass: $mass, + ); + } + + private function parseDatasheets(Crawler $dom): array + { + //Iterate over each a element withing div.pol-product-detail-download-files + $datasheets = []; + $dom->filter('div.pol-product-detail-download-files a')->each(function (Crawler $node) use (&$datasheets) { + $datasheets[] = new FileDTO($node->attr('href'), $node->text()); + }); + + return $datasheets; + } + + private function parseParameters(Crawler $dom): array + { + $parameters = []; + + //Iterate over each tr.properties-row inside table.product-detail-properties-table + $dom->filter('table.product-detail-properties-table tr.properties-row')->each(function (Crawler $node) use (&$parameters) { + $parameters[] = ParameterDTO::parseValueIncludingUnit( + name: rtrim($node->filter('th.properties-label')->text(), ':'), + value: trim($node->filter('td.properties-value')->text()) + ); + }); + + return $parameters; + } + + private function parseCategory(Crawler $dom): string + { + $category = ''; + + //Iterate over each li.breadcrumb-item inside ol.breadcrumb + $dom->filter('ol.breadcrumb li.breadcrumb-item')->each(function (Crawler $node) use (&$category) { + //Skip if it has breadcrumb-item-home class + if (str_contains($node->attr('class'), 'breadcrumb-item-home')) { + return; + } + + + $category .= $node->text() . ' -> '; + }); + + //Remove the last ' -> ' + return substr($category, 0, -4); + } + + private function parseNotes(Crawler $dom): string + { + //Concat product highlights and product description + return $dom->filter('div.product-detail-top-features')->html('') . '

' . $dom->filter('div.product-detail-description-text')->html(''); + } + + private function parsePrices(Crawler $dom): array + { + //TODO: Properly handle multiple prices, for now we just look at the price for one piece + + //We assume the currency is always the same + $currency = $dom->filter('meta[property="product:price:currency"]')->attr('content'); + + //If there is meta[property=highPrice] then use this as the price + if ($dom->filter('meta[itemprop="highPrice"]')->count() > 0) { + $price = $dom->filter('meta[itemprop="highPrice"]')->attr('content'); + } else { + $price = $dom->filter('meta[property="product:price:amount"]')->attr('content'); + } + + return [ + new PriceDTO(1.0, $price, $currency) + ]; + } + + public function getCapabilities(): array + { + return [ + ProviderCapabilities::BASIC, + ProviderCapabilities::PICTURE, + ProviderCapabilities::PRICE, + ProviderCapabilities::DATASHEET + ]; + } +} diff --git a/src/Services/InfoProviderSystem/Providers/ReicheltProvider.php b/src/Services/InfoProviderSystem/Providers/ReicheltProvider.php new file mode 100644 index 000000000..5c8efbf16 --- /dev/null +++ b/src/Services/InfoProviderSystem/Providers/ReicheltProvider.php @@ -0,0 +1,278 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Services\InfoProviderSystem\Providers; + +use App\Services\InfoProviderSystem\DTOs\FileDTO; +use App\Services\InfoProviderSystem\DTOs\ParameterDTO; +use App\Services\InfoProviderSystem\DTOs\PartDetailDTO; +use App\Services\InfoProviderSystem\DTOs\PriceDTO; +use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO; +use App\Services\InfoProviderSystem\DTOs\SearchResultDTO; +use App\Settings\InfoProviderSystem\ReicheltSettings; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\DomCrawler\Crawler; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +class ReicheltProvider implements InfoProviderInterface +{ + + public const DISTRIBUTOR_NAME = "Reichelt"; + + public function __construct(private readonly HttpClientInterface $client, + private readonly ReicheltSettings $settings, + ) + { + } + + public function getProviderInfo(): array + { + return [ + 'name' => 'Reichelt', + 'description' => 'Webscraping from reichelt.com to get part information', + 'url' => 'https://www.reichelt.com/', + 'disabled_help' => 'Enable provider in provider settings.', + 'settings_class' => ReicheltSettings::class, + ]; + } + + public function getProviderKey(): string + { + return 'reichelt'; + } + + public function isActive(): bool + { + return $this->settings->enabled; + } + + public function searchByKeyword(string $keyword): array + { + $response = $this->client->request('GET', sprintf($this->getBaseURL() . '/shop/search/%s', $keyword)); + $html = $response->getContent(); + + //Parse the HTML and return the results + $dom = new Crawler($html); + //Iterate over all div.al_gallery_article elements + $results = []; + $dom->filter('div.al_gallery_article')->each(function (Crawler $element) use (&$results) { + + //Extract product id from data-product attribute + $artId = json_decode($element->attr('data-product'), true, 2, JSON_THROW_ON_ERROR)['artid']; + + $productID = $element->filter('meta[itemprop="productID"]')->attr('content'); + $name = $element->filter('meta[itemprop="name"]')->attr('content'); + $sku = $element->filter('meta[itemprop="sku"]')->attr('content'); + + //Try to extract a picture URL: + $pictureURL = $element->filter("div.al_artlogo img")->attr('src'); + + $results[] = new SearchResultDTO( + provider_key: $this->getProviderKey(), + provider_id: $artId, + name: $productID, + description: $name, + category: null, + manufacturer: $sku, + preview_image_url: $pictureURL, + provider_url: $element->filter('a.al_artinfo_link')->attr('href') + ); + }); + + return $results; + } + + public function getDetails(string $id): PartDetailDTO + { + //Check that the ID is a number + if (!is_numeric($id)) { + throw new \InvalidArgumentException("Invalid ID"); + } + + //Use this endpoint to resolve the artID to a product page + $response = $this->client->request('GET', + sprintf( + 'https://www.reichelt.com/?ACTION=514&id=74&article=%s&LANGUAGE=%s&CCOUNTRY=%s', + $id, + strtoupper($this->settings->language), + strtoupper($this->settings->country) + ) + ); + $json = $response->toArray(); + + //Retrieve the product page from the response + $productPage = $this->getBaseURL() . '/shop/product' . $json[0]['article_path']; + + + $response = $this->client->request('GET', $productPage, [ + 'query' => [ + 'CCTYPE' => $this->settings->includeVAT ? 'private' : 'business', + 'currency' => $this->settings->currency, + ], + ]); + $html = $response->getContent(); + $dom = new Crawler($html); + + //Extract the product notes + $notes = $dom->filter('p[itemprop="description"]')->html(); + + //Extract datasheets + $datasheets = []; + $dom->filter('div.articleDatasheet a')->each(function (Crawler $element) use (&$datasheets) { + $datasheets[] = new FileDTO($element->attr('href'), $element->filter('span')->text()); + }); + + //Determine price for one unit + $priceString = $dom->filter('meta[itemprop="price"]')->attr('content'); + $currency = $dom->filter('meta[itemprop="priceCurrency"]')->attr('content', 'EUR'); + + //Create purchase info + $purchaseInfo = new PurchaseInfoDTO( + distributor_name: self::DISTRIBUTOR_NAME, + order_number: $json[0]['article_artnr'], + prices: array_merge( + [new PriceDTO(1.0, $priceString, $currency, $this->settings->includeVAT)] + , $this->parseBatchPrices($dom, $currency)), + product_url: $productPage + ); + + //Create part object + return new PartDetailDTO( + provider_key: $this->getProviderKey(), + provider_id: $id, + name: $json[0]['article_artnr'], + description: $json[0]['article_besch'], + category: $this->parseCategory($dom), + manufacturer: $json[0]['manufacturer_name'], + mpn: $this->parseMPN($dom), + preview_image_url: $json[0]['article_picture'], + provider_url: $productPage, + notes: $notes, + datasheets: $datasheets, + parameters: $this->parseParameters($dom), + vendor_infos: [$purchaseInfo] + ); + + } + + private function parseMPN(Crawler $dom): string + { + //Find the small element directly after meta[itemprop="url"] element + $element = $dom->filter('meta[itemprop="url"] + small'); + //If the text contains GTIN text, take the small element afterwards + if (str_contains($element->text(), 'GTIN')) { + $element = $dom->filter('meta[itemprop="url"] + small + small'); + } + + //The MPN is contained in the span inside the element + return $element->filter('span')->text(); + } + + private function parseBatchPrices(Crawler $dom, string $currency): array + { + //Iterate over each a.inline-block element in div.discountValue + $prices = []; + $dom->filter('div.discountValue a.inline-block')->each(function (Crawler $element) use (&$prices, $currency) { + //The minimum amount is the number in the span.block element + $minAmountText = $element->filter('span.block')->text(); + + //Extract a integer from the text + $matches = []; + if (!preg_match('/\d+/', $minAmountText, $matches)) { + return; + } + + $minAmount = (int) $matches[0]; + + //The price is the text of the p.productPrice element + $priceString = $element->filter('p.productPrice')->text(); + //Replace comma with dot + $priceString = str_replace(',', '.', $priceString); + //Strip any non-numeric characters + $priceString = preg_replace('/[^0-9.]/', '', $priceString); + + $prices[] = new PriceDTO($minAmount, $priceString, $currency, $this->settings->includeVAT); + }); + + return $prices; + } + + + private function parseCategory(Crawler $dom): string + { + // Look for ol.breadcrumb and iterate over the li elements + $category = ''; + $dom->filter('ol.breadcrumb li.triangle-left')->each(function (Crawler $element) use (&$category) { + //Do not include the .breadcrumb-showmore element + if ($element->attr('id') === 'breadcrumb-showmore') { + return; + } + + $category .= $element->text() . ' -> '; + }); + //Remove the trailing ' -> ' + $category = substr($category, 0, -4); + + return $category; + } + + /** + * @param Crawler $dom + * @return ParameterDTO[] + */ + private function parseParameters(Crawler $dom): array + { + $parameters = []; + //Iterate over each ul.articleTechnicalData which contains the specifications of each group + $dom->filter('ul.articleTechnicalData')->each(function (Crawler $groupElement) use (&$parameters) { + $groupName = $groupElement->filter('li.articleTechnicalHeadline')->text(); + + //Iterate over each second li in ul.articleAttribute, which contains the specifications + $groupElement->filter('ul.articleAttribute li:nth-child(2n)')->each(function (Crawler $specElement) use (&$parameters, $groupName) { + $parameters[] = ParameterDTO::parseValueIncludingUnit( + name: $specElement->previousAll()->text(), + value: $specElement->text(), + group: $groupName + ); + }); + }); + + return $parameters; + } + + private function getBaseURL(): string + { + //Without the trailing slash + return 'https://www.reichelt.com/' . strtolower($this->settings->country) . '/' . strtolower($this->settings->language); + } + + public function getCapabilities(): array + { + return [ + ProviderCapabilities::BASIC, + ProviderCapabilities::PICTURE, + ProviderCapabilities::DATASHEET, + ProviderCapabilities::PRICE, + ]; + } +} diff --git a/src/Services/InfoProviderSystem/Providers/TMEClient.php b/src/Services/InfoProviderSystem/Providers/TMEClient.php index 0e32e9a64..ae2ab0d14 100644 --- a/src/Services/InfoProviderSystem/Providers/TMEClient.php +++ b/src/Services/InfoProviderSystem/Providers/TMEClient.php @@ -23,6 +23,7 @@ namespace App\Services\InfoProviderSystem\Providers; +use App\Settings\InfoProviderSystem\TMESettings; use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\ResponseInterface; @@ -30,15 +31,15 @@ class TMEClient { public const BASE_URI = 'https://api.tme.eu/'; - public function __construct(private readonly HttpClientInterface $tmeClient, private readonly string $token, private readonly string $secret) + public function __construct(private readonly HttpClientInterface $tmeClient, private readonly TMESettings $settings) { } public function makeRequest(string $action, array $parameters): ResponseInterface { - $parameters['Token'] = $this->token; - $parameters['ApiSignature'] = $this->getSignature($action, $parameters, $this->secret); + $parameters['Token'] = $this->settings->apiToken; + $parameters['ApiSignature'] = $this->getSignature($action, $parameters, $this->settings->apiSecret); return $this->tmeClient->request('POST', $this->getUrlForAction($action), [ 'body' => $parameters, @@ -47,9 +48,19 @@ public function makeRequest(string $action, array $parameters): ResponseInterfac public function isUsable(): bool { - return $this->token !== '' && $this->secret !== ''; + return !($this->settings->apiToken === null || $this->settings->apiSecret === null); } + /** + * Returns true if the client is using a private (account related token) instead of a deprecated anonymous token + * to authenticate with TME. + * @return bool + */ + public function isUsingPrivateToken(): bool + { + //Private tokens are longer than anonymous ones (50 instead of 45 characters) + return strlen($this->settings->apiToken ?? '') > 45; + } /** * Generates the signature for the given action and parameters. @@ -83,4 +94,4 @@ private function sortSignatureParams(array $params): array return $params; } -} \ No newline at end of file +} diff --git a/src/Services/InfoProviderSystem/Providers/TMEProvider.php b/src/Services/InfoProviderSystem/Providers/TMEProvider.php index 61944b7d4..9bc73f098 100644 --- a/src/Services/InfoProviderSystem/Providers/TMEProvider.php +++ b/src/Services/InfoProviderSystem/Providers/TMEProvider.php @@ -30,18 +30,22 @@ use App\Services\InfoProviderSystem\DTOs\PriceDTO; use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO; use App\Services\InfoProviderSystem\DTOs\SearchResultDTO; +use App\Settings\InfoProviderSystem\TMESettings; class TMEProvider implements InfoProviderInterface { private const VENDOR_NAME = 'TME'; - public function __construct(private readonly TMEClient $tmeClient, private readonly string $country, - private readonly string $language, private readonly string $currency, - /** @var bool If true, the prices are gross prices. If false, the prices are net prices. */ - private readonly bool $get_gross_prices) + private readonly bool $get_gross_prices; + public function __construct(private readonly TMEClient $tmeClient, private readonly TMESettings $settings) { - + //If we have a private token, set get_gross_prices to false, as it is automatically determined by the account type then + if ($this->tmeClient->isUsingPrivateToken()) { + $this->get_gross_prices = false; + } else { + $this->get_gross_prices = $this->settings->grossPrices; + } } public function getProviderInfo(): array @@ -50,7 +54,8 @@ public function getProviderInfo(): array 'name' => 'TME', 'description' => 'This provider uses the API of TME (Transfer Multipart).', 'url' => 'https://tme.eu/', - 'disabled_help' => 'Configure the PROVIDER_TME_KEY and PROVIDER_TME_SECRET environment variables to use this provider.' + 'disabled_help' => 'Configure the API Token and secret in provider settings to use this provider.', + 'settings_class' => TMESettings::class ]; } @@ -67,8 +72,8 @@ public function isActive(): bool public function searchByKeyword(string $keyword): array { $response = $this->tmeClient->makeRequest('Products/Search', [ - 'Country' => $this->country, - 'Language' => $this->language, + 'Country' => $this->settings->country, + 'Language' => $this->settings->language, 'SearchPlain' => $keyword, ]); @@ -97,8 +102,8 @@ public function searchByKeyword(string $keyword): array public function getDetails(string $id): PartDetailDTO { $response = $this->tmeClient->makeRequest('Products/GetProducts', [ - 'Country' => $this->country, - 'Language' => $this->language, + 'Country' => $this->settings->country, + 'Language' => $this->settings->language, 'SymbolList' => [$id], ]); @@ -142,8 +147,8 @@ public function getDetails(string $id): PartDetailDTO public function getFiles(string $id): array { $response = $this->tmeClient->makeRequest('Products/GetProductsFiles', [ - 'Country' => $this->country, - 'Language' => $this->language, + 'Country' => $this->settings->country, + 'Language' => $this->settings->language, 'SymbolList' => [$id], ]); @@ -184,9 +189,9 @@ public function getFiles(string $id): array public function getVendorInfo(string $id, ?string $productURL = null): PurchaseInfoDTO { $response = $this->tmeClient->makeRequest('Products/GetPricesAndStocks', [ - 'Country' => $this->country, - 'Language' => $this->language, - 'Currency' => $this->currency, + 'Country' => $this->settings->country, + 'Language' => $this->settings->language, + 'Currency' => $this->settings->currency, 'GrossPrices' => $this->get_gross_prices, 'SymbolList' => [$id], ]); @@ -227,8 +232,8 @@ public function getVendorInfo(string $id, ?string $productURL = null): PurchaseI public function getParameters(string $id, string|null &$footprint_name = null): array { $response = $this->tmeClient->makeRequest('Products/GetParameters', [ - 'Country' => $this->country, - 'Language' => $this->language, + 'Country' => $this->settings->country, + 'Language' => $this->settings->language, 'SymbolList' => [$id], ]); @@ -291,4 +296,4 @@ public function getCapabilities(): array ProviderCapabilities::PRICE, ]; } -} \ No newline at end of file +} diff --git a/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php b/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php index 7ceb30dd9..3df7d227c 100644 --- a/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php +++ b/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php @@ -95,6 +95,11 @@ public function get1DBarcodeContent(AbstractDBElement $target): string return $prefix.$id; } + /** + * @param array $map + * @param object $target + * @return string + */ private function classToString(array $map, object $target): string { $class = $target::class; diff --git a/src/Services/LabelSystem/LabelHTMLGenerator.php b/src/Services/LabelSystem/LabelHTMLGenerator.php index 42aa1e728..8a5201ffb 100644 --- a/src/Services/LabelSystem/LabelHTMLGenerator.php +++ b/src/Services/LabelSystem/LabelHTMLGenerator.php @@ -42,6 +42,7 @@ namespace App\Services\LabelSystem; use App\Entity\LabelSystem\LabelProcessMode; +use App\Settings\SystemSettings\CustomizationSettings; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Contracts\NamedElementInterface; use App\Entity\LabelSystem\LabelOptions; @@ -60,7 +61,7 @@ public function __construct( private readonly LabelBarcodeGenerator $barcodeGenerator, private readonly SandboxedTwigFactory $sandboxedTwigProvider, private readonly Security $security, - private readonly string $partdb_title) + private readonly CustomizationSettings $customizationSettings,) { } @@ -88,7 +89,8 @@ public function getLabelHTML(LabelOptions $options, array $elements): string 'page' => $page, 'last_page' => count($elements), 'user' => $current_user, - 'install_title' => $this->partdb_title, + 'install_title' => $this->customizationSettings->instanceName, + 'partdb_title' => $this->customizationSettings->instanceName, 'paper_width' => $options->getWidth(), 'paper_height' => $options->getHeight(), ] diff --git a/src/Services/LabelSystem/PlaceholderProviders/BarcodeProvider.php b/src/Services/LabelSystem/PlaceholderProviders/BarcodeProvider.php index dd70177f9..400fef356 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/BarcodeProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/BarcodeProvider.php @@ -63,12 +63,24 @@ public function replace(string $placeholder, object $label_target, array $option return $this->barcodeGenerator->generateHTMLBarcode($label_options, $label_target); } + if ('[[BARCODE_DATAMATRIX]]' === $placeholder) { + $label_options = new LabelOptions(); + $label_options->setBarcodeType(BarcodeType::DATAMATRIX); + return $this->barcodeGenerator->generateHTMLBarcode($label_options, $label_target); + } + if ('[[BARCODE_C39]]' === $placeholder) { $label_options = new LabelOptions(); $label_options->setBarcodeType(BarcodeType::CODE39); return $this->barcodeGenerator->generateHTMLBarcode($label_options, $label_target); } + if ('[[BARCODE_C93]]' === $placeholder) { + $label_options = new LabelOptions(); + $label_options->setBarcodeType(BarcodeType::CODE93); + return $this->barcodeGenerator->generateHTMLBarcode($label_options, $label_target); + } + if ('[[BARCODE_C128]]' === $placeholder) { $label_options = new LabelOptions(); $label_options->setBarcodeType(BarcodeType::CODE128); diff --git a/src/Services/LabelSystem/PlaceholderProviders/GlobalProviders.php b/src/Services/LabelSystem/PlaceholderProviders/GlobalProviders.php index ddd4dbf17..f14a58630 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/GlobalProviders.php +++ b/src/Services/LabelSystem/PlaceholderProviders/GlobalProviders.php @@ -41,6 +41,7 @@ namespace App\Services\LabelSystem\PlaceholderProviders; +use App\Settings\SystemSettings\CustomizationSettings; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\UserSystem\User; use DateTime; @@ -54,14 +55,18 @@ */ final class GlobalProviders implements PlaceholderProviderInterface { - public function __construct(private readonly string $partdb_title, private readonly Security $security, private readonly UrlGeneratorInterface $url_generator) + public function __construct( + private readonly Security $security, + private readonly UrlGeneratorInterface $url_generator, + private CustomizationSettings $customizationSettings, + ) { } public function replace(string $placeholder, object $label_target, array $options = []): ?string { if ('[[INSTALL_NAME]]' === $placeholder) { - return $this->partdb_title; + return $this->customizationSettings->instanceName; } $user = $this->security->getUser(); diff --git a/src/Services/LabelSystem/PlaceholderProviders/PartProvider.php b/src/Services/LabelSystem/PlaceholderProviders/PartProvider.php index 0df4d3d73..7d9e4db59 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/PartProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/PartProvider.php @@ -46,7 +46,9 @@ use App\Entity\Parts\Footprint; use App\Entity\Parts\Part; use App\Services\Formatters\SIFormatter; -use Parsedown; +use League\CommonMark\Environment\Environment; +use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension; +use League\CommonMark\MarkdownConverter; use Symfony\Contracts\Translation\TranslatorInterface; /** @@ -54,8 +56,13 @@ */ final class PartProvider implements PlaceholderProviderInterface { + private readonly MarkdownConverter $inlineConverter; + public function __construct(private readonly SIFormatter $siFormatter, private readonly TranslatorInterface $translator) { + $environment = new Environment(); + $environment->addExtension(new InlinesOnlyExtension()); + $this->inlineConverter = new MarkdownConverter($environment); } public function replace(string $placeholder, object $part, array $options = []): ?string @@ -112,22 +119,20 @@ public function replace(string $placeholder, object $part, array $options = []): return $this->translator->trans($part->getManufacturingStatus()->toTranslationKey()); } - $parsedown = new Parsedown(); - if ('[[DESCRIPTION]]' === $placeholder) { - return $parsedown->line($part->getDescription()); + return trim($this->inlineConverter->convert($part->getDescription())->getContent()); } if ('[[DESCRIPTION_T]]' === $placeholder) { - return strip_tags((string) $parsedown->line($part->getDescription())); + return trim(strip_tags($this->inlineConverter->convert($part->getDescription())->getContent())); } if ('[[COMMENT]]' === $placeholder) { - return $parsedown->line($part->getComment()); + return trim($this->inlineConverter->convert($part->getComment())->getContent()); } if ('[[COMMENT_T]]' === $placeholder) { - return strip_tags((string) $parsedown->line($part->getComment())); + return trim(strip_tags($this->inlineConverter->convert($part->getComment())->getContent())); } return null; diff --git a/src/Services/LabelSystem/SandboxedTwigFactory.php b/src/Services/LabelSystem/SandboxedTwigFactory.php index cdf0594f7..d5e09fa54 100644 --- a/src/Services/LabelSystem/SandboxedTwigFactory.php +++ b/src/Services/LabelSystem/SandboxedTwigFactory.php @@ -122,8 +122,8 @@ final class SandboxedTwigFactory 'getFullPath', 'getPathArray', 'getSubelements', 'getChildren', 'isNotSelectable', ], AbstractCompany::class => ['getAddress', 'getPhoneNumber', 'getFaxNumber', 'getEmailAddress', 'getWebsite', 'getAutoProductUrl'], AttachmentContainingDBElement::class => ['getAttachments', 'getMasterPictureAttachment'], - Attachment::class => ['isPicture', 'is3DModel', 'isExternal', 'isSecure', 'isBuiltIn', 'getExtension', - 'getElement', 'getURL', 'getHost', 'getFilename', 'getAttachmentType', 'getShowInTable', ], + Attachment::class => ['isPicture', 'is3DModel', 'hasExternal', 'hasInternal', 'isSecure', 'isBuiltIn', 'getExtension', + 'getElement', 'getExternalPath', 'getHost', 'getFilename', 'getAttachmentType', 'getShowInTable'], AbstractParameter::class => ['getFormattedValue', 'getGroup', 'getSymbol', 'getValueMin', 'getValueMax', 'getValueTypical', 'getUnit', 'getValueText', ], MeasurementUnit::class => ['getUnit', 'isInteger', 'useSIPrefix'], @@ -133,7 +133,7 @@ final class SandboxedTwigFactory Supplier::class => ['getShippingCosts', 'getDefaultCurrency'], Part::class => ['isNeedsReview', 'getTags', 'getMass', 'getIpn', 'getProviderReference', 'getDescription', 'getComment', 'isFavorite', 'getCategory', 'getFootprint', - 'getPartLots', 'getPartUnit', 'useFloatAmount', 'getMinAmount', 'getAmountSum', 'isNotEnoughInstock', 'isAmountUnknown', 'getExpiredAmountSum', + 'getPartLots', 'getPartUnit', 'getPartCustomState', 'useFloatAmount', 'getMinAmount', 'getAmountSum', 'isNotEnoughInstock', 'isAmountUnknown', 'getExpiredAmountSum', 'getManufacturerProductUrl', 'getCustomProductURL', 'getManufacturingStatus', 'getManufacturer', 'getManufacturerProductNumber', 'getOrderdetails', 'isObsolete', 'getParameters', 'getGroupedParameters', diff --git a/src/Services/LogSystem/EventCommentNeededHelper.php b/src/Services/LogSystem/EventCommentNeededHelper.php index 8440f199f..cacf525f1 100644 --- a/src/Services/LogSystem/EventCommentNeededHelper.php +++ b/src/Services/LogSystem/EventCommentNeededHelper.php @@ -22,37 +22,25 @@ */ namespace App\Services\LogSystem; +use App\Settings\SystemSettings\HistorySettings; + /** * This service is used to check if a log change comment is needed for a given operation type. * It is configured using the "enforce_change_comments_for" config parameter. * @see \App\Tests\Services\LogSystem\EventCommentNeededHelperTest */ -class EventCommentNeededHelper +final class EventCommentNeededHelper { - final public const VALID_OPERATION_TYPES = [ - 'part_edit', - 'part_create', - 'part_delete', - 'part_stock_operation', - 'datastructure_edit', - 'datastructure_create', - 'datastructure_delete', - ]; - - public function __construct(protected array $enforce_change_comments_for) + public function __construct(private readonly HistorySettings $settings) { + } /** * Checks if a log change comment is needed for the given operation type */ - public function isCommentNeeded(string $comment_type): bool + public function isCommentNeeded(EventCommentType $comment_type): bool { - //Check if the comment type is valid - if (! in_array($comment_type, self::VALID_OPERATION_TYPES, true)) { - throw new \InvalidArgumentException('The comment type "'.$comment_type.'" is not valid!'); - } - - return in_array($comment_type, $this->enforce_change_comments_for, true); + return in_array($comment_type, $this->settings->enforceComments, true); } } diff --git a/src/Services/LogSystem/EventCommentType.php b/src/Services/LogSystem/EventCommentType.php new file mode 100644 index 000000000..d68c03b25 --- /dev/null +++ b/src/Services/LogSystem/EventCommentType.php @@ -0,0 +1,47 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Services\LogSystem; + +use Symfony\Contracts\Translation\TranslatableInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * This enum represents the different types of event comments that could be required, by the system. + * They are almost only useful when working with the EventCommentNeededHelper service. + */ +enum EventCommentType: string implements TranslatableInterface +{ + case PART_EDIT = 'part_edit'; + case PART_CREATE = 'part_create'; + case PART_DELETE = 'part_delete'; + case PART_STOCK_OPERATION = 'part_stock_operation'; + case DATASTRUCTURE_EDIT = 'datastructure_edit'; + case DATASTRUCTURE_CREATE = 'datastructure_create'; + case DATASTRUCTURE_DELETE = 'datastructure_delete'; + + public function trans(TranslatorInterface $translator, ?string $locale = null): string + { + return $translator->trans('settings.system.history.enforceComments.type.' . $this->value, locale: $locale); + } +} diff --git a/src/Services/Parts/PartsTableActionHandler.php b/src/Services/Parts/PartsTableActionHandler.php index 1bd6209dd..945cff7b7 100644 --- a/src/Services/Parts/PartsTableActionHandler.php +++ b/src/Services/Parts/PartsTableActionHandler.php @@ -22,6 +22,7 @@ */ namespace App\Services\Parts; +use App\Entity\Parts\StorageLocation; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; @@ -29,13 +30,14 @@ use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; -use App\Repository\PartRepository; use Doctrine\ORM\EntityManagerInterface; use InvalidArgumentException; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\Exception\AccessDeniedException; +use function Symfony\Component\Translation\t; + final class PartsTableActionHandler { public function __construct(private readonly EntityManagerInterface $entityManager, private readonly Security $security, private readonly UrlGeneratorInterface $urlGenerator) @@ -61,8 +63,9 @@ public function idStringToArray(string $ids): array /** * @param Part[] $selected_parts * @return RedirectResponse|null Returns a redirect response if the user should be redirected to another page, otherwise null + * //@param-out list|array $errors */ - public function handleAction(string $action, array $selected_parts, ?int $target_id, ?string $redirect_url = null): ?RedirectResponse + public function handleAction(string $action, array $selected_parts, ?int $target_id, ?string $redirect_url = null, array &$errors = []): ?RedirectResponse { if ($action === 'add_to_project') { return new RedirectResponse( @@ -95,7 +98,7 @@ public function handleAction(string $action, array $selected_parts, ?int $target //When action starts with "export_" we have to redirect to the export controller $matches = []; - if (preg_match('/^export_(json|yaml|xml|csv)$/', $action, $matches)) { + if (preg_match('/^export_(json|yaml|xml|csv|xlsx)$/', $action, $matches)) { $ids = implode(',', array_map(static fn (Part $part) => $part->getID(), $selected_parts)); $level = match ($target_id) { 2 => 'extended', @@ -114,6 +117,16 @@ public function handleAction(string $action, array $selected_parts, ?int $target ); } + if ($action === 'bulk_info_provider_import') { + $ids = implode(',', array_map(static fn (Part $part) => $part->getID(), $selected_parts)); + return new RedirectResponse( + $this->urlGenerator->generate('bulk_info_provider_step1', [ + 'ids' => $ids, + '_redirect' => $redirect_url + ]) + ); + } + //Iterate over the parts and apply the action to it: foreach ($selected_parts as $part) { @@ -161,6 +174,29 @@ public function handleAction(string $action, array $selected_parts, ?int $target $this->denyAccessUnlessGranted('@measurement_units.read'); $part->setPartUnit(null === $target_id ? null : $this->entityManager->find(MeasurementUnit::class, $target_id)); break; + case 'change_location': + $this->denyAccessUnlessGranted('@storelocations.read'); + //Retrieve the first part lot and set the location for it + $part_lots = $part->getPartLots(); + if ($part_lots->count() > 0) { + if ($part_lots->count() > 1) { + $errors[] = [ + 'part' => $part, + 'message' => t('parts.table.action_handler.error.part_lots_multiple'), + ]; + break; + } + + $part_lot = $part_lots->first(); + $part_lot->setStorageLocation(null === $target_id ? null : $this->entityManager->find(StorageLocation::class, $target_id)); + } else { //Create a new part lot if there are none + $part_lot = new PartLot(); + $part_lot->setPart($part); + $part_lot->setInstockUnknown(true); //We do not know how many parts are in stock, so we set it to true + $part_lot->setStorageLocation(null === $target_id ? null : $this->entityManager->find(StorageLocation::class, $target_id)); + $this->entityManager->persist($part_lot); + } + break; default: throw new InvalidArgumentException('The given action is unknown! ('.$action.')'); diff --git a/src/Services/Parts/PricedetailHelper.php b/src/Services/Parts/PricedetailHelper.php index 092cc2787..b2e1340ff 100644 --- a/src/Services/Parts/PricedetailHelper.php +++ b/src/Services/Parts/PricedetailHelper.php @@ -25,6 +25,7 @@ use App\Entity\Parts\Part; use App\Entity\PriceInformations\Currency; use App\Entity\PriceInformations\Pricedetail; +use App\Settings\SystemSettings\LocalizationSettings; use Brick\Math\BigDecimal; use Brick\Math\RoundingMode; use Doctrine\ORM\PersistentCollection; @@ -39,7 +40,7 @@ class PricedetailHelper { protected string $locale; - public function __construct(protected string $base_currency) + public function __construct() { $this->locale = Locale::getDefault(); } diff --git a/src/Services/ProjectSystem/ProjectBuildHelper.php b/src/Services/ProjectSystem/ProjectBuildHelper.php index 269c7e4c2..a541c29d1 100644 --- a/src/Services/ProjectSystem/ProjectBuildHelper.php +++ b/src/Services/ProjectSystem/ProjectBuildHelper.php @@ -31,9 +31,9 @@ /** * @see \App\Tests\Services\ProjectSystem\ProjectBuildHelperTest */ -class ProjectBuildHelper +final readonly class ProjectBuildHelper { - public function __construct(private readonly PartLotWithdrawAddHelper $withdraw_add_helper) + public function __construct(private PartLotWithdrawAddHelper $withdraw_add_helper) { } @@ -63,20 +63,37 @@ public function getMaximumBuildableCountForBOMEntry(ProjectBOMEntry $projectBOME */ public function getMaximumBuildableCount(Project $project): int { + $bom_entries = $project->getBomEntries(); + if ($bom_entries->isEmpty()) { + return 0; + } $maximum_buildable_count = PHP_INT_MAX; - foreach ($project->getBomEntries() as $bom_entry) { + foreach ($bom_entries as $bom_entry) { //Skip BOM entries without a part (as we can not determine that) if (!$bom_entry->isPartBomEntry()) { continue; } - //The maximum buildable count for the whole project is the minimum of all BOM entries $maximum_buildable_count = min($maximum_buildable_count, $this->getMaximumBuildableCountForBOMEntry($bom_entry)); } - return $maximum_buildable_count; } + /** + * Returns the maximum buildable amount of the given project as string, based on the stock of the used parts in the BOM. + * If the maximum buildable count is infinite, the string 'โˆž' is returned. + * @param Project $project + * @return string + */ + public function getMaximumBuildableCountAsString(Project $project): string + { + $max_count = $this->getMaximumBuildableCount($project); + if ($max_count === PHP_INT_MAX) { + return 'โˆž'; + } + return (string) $max_count; + } + /** * Checks if the given project can be built with the current stock. * This means that the maximum buildable count is greater or equal than the requested $number_of_projects diff --git a/src/Services/System/BannerHelper.php b/src/Services/System/BannerHelper.php index 3d5daef99..bb27158fb 100644 --- a/src/Services/System/BannerHelper.php +++ b/src/Services/System/BannerHelper.php @@ -23,12 +23,14 @@ namespace App\Services\System; +use App\Settings\SystemSettings\CustomizationSettings; + /** * Helper service to retrieve the banner of this Part-DB installation */ class BannerHelper { - public function __construct(private readonly string $project_dir, private readonly string $partdb_banner) + public function __construct(private readonly CustomizationSettings $customizationSettings) { } @@ -39,18 +41,6 @@ public function __construct(private readonly string $project_dir, private readon */ public function getBanner(): string { - $banner = $this->partdb_banner; - if ($banner === '') { - $banner_path = $this->project_dir - .DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'banner.md'; - - $tmp = file_get_contents($banner_path); - if (false === $tmp) { - throw new \RuntimeException('The banner file could not be read.'); - } - $banner = $tmp; - } - - return $banner; + return $this->customizationSettings->banner ?? ""; } -} \ No newline at end of file +} diff --git a/src/Services/System/UpdateAvailableManager.php b/src/Services/System/UpdateAvailableManager.php index 31cb32663..82cfb84e5 100644 --- a/src/Services/System/UpdateAvailableManager.php +++ b/src/Services/System/UpdateAvailableManager.php @@ -23,6 +23,7 @@ namespace App\Services\System; +use App\Settings\SystemSettings\PrivacySettings; use Psr\Log\LoggerInterface; use Shivas\VersioningBundle\Service\VersionManagerInterface; use Symfony\Component\DependencyInjection\Attribute\Autowire; @@ -43,7 +44,7 @@ class UpdateAvailableManager public function __construct(private readonly HttpClientInterface $httpClient, private readonly CacheInterface $updateCache, private readonly VersionManagerInterface $versionManager, - private readonly bool $check_for_updates, private readonly LoggerInterface $logger, + private readonly PrivacySettings $privacySettings, private readonly LoggerInterface $logger, #[Autowire(param: 'kernel.debug')] private readonly bool $is_dev_mode) { @@ -83,7 +84,7 @@ public function getLatestVersionUrl(): string public function isUpdateAvailable(): bool { //If we don't want to check for updates, we can return false - if (!$this->check_for_updates) { + if (!$this->privacySettings->checkForUpdates) { return false; } @@ -101,7 +102,7 @@ public function isUpdateAvailable(): bool private function getLatestVersionInfo(): array { //If we don't want to check for updates, we can return dummy data - if (!$this->check_for_updates) { + if (!$this->privacySettings->checkForUpdates) { return [ 'version' => '0.0.1', 'url' => 'update-checking-disabled' diff --git a/src/Services/Tools/ExchangeRateUpdater.php b/src/Services/Tools/ExchangeRateUpdater.php index eac6de16f..6eb7ec131 100644 --- a/src/Services/Tools/ExchangeRateUpdater.php +++ b/src/Services/Tools/ExchangeRateUpdater.php @@ -23,13 +23,16 @@ namespace App\Services\Tools; use App\Entity\PriceInformations\Currency; +use App\Settings\SystemSettings\LocalizationSettings; use Brick\Math\BigDecimal; use Brick\Math\RoundingMode; +use Exchanger\Exception\UnsupportedCurrencyPairException; +use Exchanger\Exception\UnsupportedExchangeQueryException; use Swap\Swap; class ExchangeRateUpdater { - public function __construct(private readonly string $base_currency, private readonly Swap $swap) + public function __construct(private LocalizationSettings $localizationSettings, private readonly Swap $swap) { } @@ -38,15 +41,21 @@ public function __construct(private readonly string $base_currency, private read */ public function update(Currency $currency): Currency { - //Currency pairs are always in the format "BASE/QUOTE" - $rate = $this->swap->latest($this->base_currency.'/'.$currency->getIsoCode()); - //The rate says how many quote units are worth one base unit - //So we need to invert it to get the exchange rate - - $rate_bd = BigDecimal::of($rate->getValue()); - $rate_inverted = BigDecimal::one()->dividedBy($rate_bd, Currency::PRICE_SCALE, RoundingMode::HALF_UP); - - $currency->setExchangeRate($rate_inverted); + try { + //Try it in the direction QUOTE/BASE first, as most providers provide rates in this direction + $rate = $this->swap->latest($currency->getIsoCode().'/'.$this->localizationSettings->baseCurrency); + $effective_rate = BigDecimal::of($rate->getValue()); + } catch (UnsupportedCurrencyPairException|UnsupportedExchangeQueryException $exception) { + //Otherwise try to get it inverse and calculate it ourselfes, from the format "BASE/QUOTE" + $rate = $this->swap->latest($this->localizationSettings->baseCurrency.'/'.$currency->getIsoCode()); + //The rate says how many quote units are worth one base unit + //So we need to invert it to get the exchange rate + + $rate_bd = BigDecimal::of($rate->getValue()); + $effective_rate = BigDecimal::one()->dividedBy($rate_bd, Currency::PRICE_SCALE, RoundingMode::HALF_UP); + } + + $currency->setExchangeRate($effective_rate); return $currency; } diff --git a/src/Services/Trees/ToolsTreeBuilder.php b/src/Services/Trees/ToolsTreeBuilder.php index 185713060..56064ba46 100644 --- a/src/Services/Trees/ToolsTreeBuilder.php +++ b/src/Services/Trees/ToolsTreeBuilder.php @@ -29,6 +29,7 @@ use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; +use App\Entity\Parts\PartCustomState; use App\Entity\Parts\StorageLocation; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; @@ -138,6 +139,11 @@ protected function getToolsNode(): array $this->translator->trans('info_providers.search.title'), $this->urlGenerator->generate('info_providers_search') ))->setIcon('fa-treeview fa-fw fa-solid fa-cloud-arrow-down'); + + $nodes[] = (new TreeViewNode( + $this->translator->trans('info_providers.bulk_import.manage_jobs'), + $this->urlGenerator->generate('bulk_info_provider_manage') + ))->setIcon('fa-treeview fa-fw fa-solid fa-tasks'); } return $nodes; @@ -212,6 +218,12 @@ protected function getEditNodes(): array $this->urlGenerator->generate('label_profile_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-qrcode'); } + if ($this->security->isGranted('read', new PartCustomState())) { + $nodes[] = (new TreeViewNode( + $this->translator->trans('tree.tools.edit.part_custom_state'), + $this->urlGenerator->generate('part_custom_state_new') + ))->setIcon('fa-fw fa-treeview fa-solid fa-tools'); + } if ($this->security->isGranted('create', new Part())) { $nodes[] = (new TreeViewNode( $this->translator->trans('tree.tools.edit.part'), @@ -289,6 +301,13 @@ protected function getSystemNodes(): array ))->setIcon('fa-fw fa-treeview fa-solid fa-database'); } + if ($this->security->isGranted('@config.change_system_settings')) { + $nodes[] = (new TreeViewNode( + $this->translator->trans('tree.tools.system.settings'), + $this->urlGenerator->generate('system_settings') + ))->setIcon('fa fa-fw fa-gears fa-solid'); + } + return $nodes; } } diff --git a/src/Services/Trees/TreeViewGenerator.php b/src/Services/Trees/TreeViewGenerator.php index 23d6a4069..73ffa5baf 100644 --- a/src/Services/Trees/TreeViewGenerator.php +++ b/src/Services/Trees/TreeViewGenerator.php @@ -38,6 +38,7 @@ use App\Services\Cache\ElementCacheTagGenerator; use App\Services\Cache\UserCacheKeyGenerator; use App\Services\EntityURLGenerator; +use App\Settings\BehaviorSettings\SidebarSettings; use Doctrine\ORM\EntityManagerInterface; use InvalidArgumentException; use RecursiveIteratorIterator; @@ -53,6 +54,10 @@ */ class TreeViewGenerator { + + private readonly bool $rootNodeExpandedByDefault; + private readonly bool $rootNodeEnabled; + public function __construct( protected EntityURLGenerator $urlGenerator, protected EntityManagerInterface $em, @@ -61,10 +66,10 @@ public function __construct( protected UserCacheKeyGenerator $keyGenerator, protected TranslatorInterface $translator, private readonly UrlGeneratorInterface $router, - protected bool $rootNodeExpandedByDefault, - protected bool $rootNodeEnabled, - + private readonly SidebarSettings $sidebarSettings, ) { + $this->rootNodeEnabled = $this->sidebarSettings->rootNodeEnabled; + $this->rootNodeExpandedByDefault = $this->sidebarSettings->rootNodeExpanded; } /** @@ -174,10 +179,7 @@ private function getTreeViewUncached( } if (($mode === 'list_parts_root' || $mode === 'devices') && $this->rootNodeEnabled) { - //We show the root node as a link to the list of all parts - $show_all_parts_url = $this->router->generate('parts_show_all'); - - $root_node = new TreeViewNode($this->entityClassToRootNodeString($class), $show_all_parts_url, $generic); + $root_node = new TreeViewNode($this->entityClassToRootNodeString($class), $this->entityClassToRootNodeHref($class), $generic); $root_node->setExpanded($this->rootNodeExpandedByDefault); $root_node->setIcon($this->entityClassToRootNodeIcon($class)); @@ -187,6 +189,27 @@ private function getTreeViewUncached( return array_merge($head, $generic); } + protected function entityClassToRootNodeHref(string $class): ?string + { + //If the root node should redirect to the new entity page, we return the URL for the new entity. + if ($this->sidebarSettings->rootNodeRedirectsToNewEntity) { + return match ($class) { + Category::class => $this->router->generate('category_new'), + StorageLocation::class => $this->router->generate('store_location_new'), + Footprint::class => $this->router->generate('footprint_new'), + Manufacturer::class => $this->router->generate('manufacturer_new'), + Supplier::class => $this->router->generate('supplier_new'), + Project::class => $this->router->generate('project_new'), + default => null, + }; + } + + return match ($class) { + Project::class => $this->router->generate('project_new'), + default => $this->router->generate('parts_show_all') + }; + } + protected function entityClassToRootNodeString(string $class): string { return match ($class) { diff --git a/src/Services/UserSystem/PermissionPresetsHelper.php b/src/Services/UserSystem/PermissionPresetsHelper.php index eeb80f61f..a3ed01b82 100644 --- a/src/Services/UserSystem/PermissionPresetsHelper.php +++ b/src/Services/UserSystem/PermissionPresetsHelper.php @@ -102,9 +102,13 @@ private function admin(HasPermissionsInterface $perm_holder): void $this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'attachment_types', PermissionData::ALLOW); $this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'currencies', PermissionData::ALLOW); $this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'measurement_units', PermissionData::ALLOW); + $this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'part_custom_states', PermissionData::ALLOW); $this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'suppliers', PermissionData::ALLOW); $this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'projects', PermissionData::ALLOW); + //Allow to change system settings + $this->permissionResolver->setPermission($perm_holder, 'config', 'change_system_settings', PermissionData::ALLOW); + //Allow to manage Oauth tokens $this->permissionResolver->setPermission($perm_holder, 'system', 'manage_oauth_tokens', PermissionData::ALLOW); //Allow to show updates @@ -128,6 +132,7 @@ private function editor(HasPermissionsInterface $permHolder): HasPermissionsInte $this->permissionResolver->setAllOperationsOfPermissionExcept($permHolder, 'attachment_types', PermissionData::ALLOW, ['import']); $this->permissionResolver->setAllOperationsOfPermissionExcept($permHolder, 'currencies', PermissionData::ALLOW, ['import']); $this->permissionResolver->setAllOperationsOfPermissionExcept($permHolder, 'measurement_units', PermissionData::ALLOW, ['import']); + $this->permissionResolver->setAllOperationsOfPermissionExcept($permHolder, 'part_custom_states', PermissionData::ALLOW, ['import']); $this->permissionResolver->setAllOperationsOfPermissionExcept($permHolder, 'suppliers', PermissionData::ALLOW, ['import']); $this->permissionResolver->setAllOperationsOfPermissionExcept($permHolder, 'projects', PermissionData::ALLOW, ['import']); diff --git a/src/Services/UserSystem/UserAvatarHelper.php b/src/Services/UserSystem/UserAvatarHelper.php index a694fa771..9dbe9c121 100644 --- a/src/Services/UserSystem/UserAvatarHelper.php +++ b/src/Services/UserSystem/UserAvatarHelper.php @@ -30,6 +30,7 @@ use App\Entity\UserSystem\User; use App\Services\Attachments\AttachmentSubmitHandler; use App\Services\Attachments\AttachmentURLGenerator; +use App\Settings\SystemSettings\PrivacySettings; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Asset\Packages; use Symfony\Component\HttpFoundation\File\UploadedFile; @@ -42,7 +43,7 @@ class UserAvatarHelper public const IMG_DEFAULT_AVATAR_PATH = 'img/default_avatar.svg'; public function __construct( - private readonly bool $use_gravatar, + private readonly PrivacySettings $privacySettings, private readonly Packages $packages, private readonly AttachmentURLGenerator $attachmentURLGenerator, private readonly EntityManagerInterface $entityManager, @@ -65,7 +66,7 @@ public function getAvatarURL(User $user): string } //If not check if gravatar is enabled (then use gravatar URL) - if ($this->use_gravatar) { + if ($this->privacySettings->useGravatar) { return $this->getGravatar($user, 200); //200px wide picture } @@ -82,7 +83,7 @@ public function getAvatarSmURL(User $user): string } //If not check if gravatar is enabled (then use gravatar URL) - if ($this->use_gravatar) { + if ($this->privacySettings->useGravatar) { return $this->getGravatar($user, 50); //50px wide picture } @@ -99,7 +100,7 @@ public function getAvatarMdURL(User $user): string } //If not check if gravatar is enabled (then use gravatar URL) - if ($this->use_gravatar) { + if ($this->privacySettings->useGravatar) { return $this->getGravatar($user, 150); } diff --git a/src/Services/UserSystem/VoterHelper.php b/src/Services/UserSystem/VoterHelper.php index 644351f4c..d3c5368c7 100644 --- a/src/Services/UserSystem/VoterHelper.php +++ b/src/Services/UserSystem/VoterHelper.php @@ -28,6 +28,9 @@ use App\Security\ApiTokenAuthenticatedToken; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; +use Symfony\Contracts\Translation\TranslatorInterface; /** * @see \App\Tests\Services\UserSystem\VoterHelperTest @@ -35,10 +38,14 @@ final class VoterHelper { private readonly UserRepository $userRepository; + private readonly array $permissionStructure; - public function __construct(private readonly PermissionManager $permissionManager, private readonly EntityManagerInterface $entityManager) + public function __construct(private readonly PermissionManager $permissionManager, + private readonly TranslatorInterface $translator, + private readonly EntityManagerInterface $entityManager) { $this->userRepository = $this->entityManager->getRepository(User::class); + $this->permissionStructure = $this->permissionManager->getPermissionStructure(); } /** @@ -47,11 +54,16 @@ public function __construct(private readonly PermissionManager $permissionManage * @param TokenInterface $token The token to check * @param string $permission The permission to check * @param string $operation The operation to check + * @param Vote|null $vote The vote object to add reasons to (optional). If null, no reasons are added. * @return bool */ - public function isGranted(TokenInterface $token, string $permission, string $operation): bool + public function isGranted(TokenInterface $token, string $permission, string $operation, ?Vote $vote = null): bool { - return $this->isGrantedTrinary($token, $permission, $operation) ?? false; + $tmp = $this->isGrantedTrinary($token, $permission, $operation) ?? false; + if ($tmp === false) { + $this->addReason($vote, $permission, $operation); + } + return $tmp; } /** @@ -124,4 +136,17 @@ public function isValidOperation(string $permission, string $operation): bool { return $this->permissionManager->isValidOperation($permission, $operation); } -} \ No newline at end of file + + public function addReason(?Vote $voter, string $permission, $operation): void + { + if ($voter !== null) { + $voter->addReason(sprintf("User does not have permission %s -> %s -> %s (%s.%s).", + $this->translator->trans('perm.group.'.($this->permissionStructure['perms'][$permission]['group'] ?? 'unknown') ), + $this->translator->trans($this->permissionStructure['perms'][$permission]['label'] ?? $permission), + $this->translator->trans($this->permissionStructure['perms'][$permission]['operations'][$operation]['label'] ?? $operation), + $permission, + $operation + )); + } + } +} diff --git a/src/Settings/AppSettings.php b/src/Settings/AppSettings.php new file mode 100644 index 000000000..42831d087 --- /dev/null +++ b/src/Settings/AppSettings.php @@ -0,0 +1,52 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings; + +use App\Settings\BehaviorSettings\BehaviorSettings; +use App\Settings\InfoProviderSystem\InfoProviderSettings; +use App\Settings\MiscSettings\MiscSettings; +use App\Settings\SystemSettings\SystemSettings; +use Jbtronics\SettingsBundle\Settings\EmbeddedSettings; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsTrait; + +#[Settings] +#[SettingsIcon('folder-tree')] +class AppSettings +{ + use SettingsTrait; + + + #[EmbeddedSettings()] + public ?SystemSettings $system = null; + + #[EmbeddedSettings()] + public ?BehaviorSettings $behavior = null; + + #[EmbeddedSettings()] + public ?InfoProviderSettings $infoProviders = null; + + #[EmbeddedSettings()] + public ?MiscSettings $miscSettings = null; +} diff --git a/src/Settings/BehaviorSettings/BehaviorSettings.php b/src/Settings/BehaviorSettings/BehaviorSettings.php new file mode 100644 index 000000000..3053073f2 --- /dev/null +++ b/src/Settings/BehaviorSettings/BehaviorSettings.php @@ -0,0 +1,44 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\BehaviorSettings; + +use Jbtronics\SettingsBundle\Settings\EmbeddedSettings; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsTrait; +use Symfony\Component\Translation\TranslatableMessage as TM; + +#[Settings(label: new TM("settings.behavior"))] +class BehaviorSettings +{ + use SettingsTrait; + + #[EmbeddedSettings] + public ?SidebarSettings $sidebar = null; + + #[EmbeddedSettings] + public ?TableSettings $table = null; + + #[EmbeddedSettings] + public ?PartInfoSettings $partInfo = null; +} diff --git a/src/Settings/BehaviorSettings/PartInfoSettings.php b/src/Settings/BehaviorSettings/PartInfoSettings.php new file mode 100644 index 000000000..f017c8463 --- /dev/null +++ b/src/Settings/BehaviorSettings/PartInfoSettings.php @@ -0,0 +1,49 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\BehaviorSettings; + +use App\Settings\SettingsIcon; +use Jbtronics\SettingsBundle\Metadata\EnvVarMode; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsParameter; +use Symfony\Component\Translation\TranslatableMessage as TM; + +#[Settings(name: "part_info", label: new TM("settings.behavior.part_info"))] +#[SettingsIcon('fa-circle-info')] +class PartInfoSettings +{ + /** + * Whether to show the part image overlays in the part info view + * @var bool + */ + #[SettingsParameter(label: new TM("settings.behavior.part_info.show_part_image_overlay"), description: new TM("settings.behavior.part_info.show_part_image_overlay.help"), + envVar: "bool:SHOW_PART_IMAGE_OVERLAY", envVarMode: EnvVarMode::OVERWRITE)] + public bool $showPartImageOverlay = true; + + #[SettingsParameter(label: new TM("settings.behavior.part_info.extract_params_from_description"))] + public bool $extractParamsFromDescription = true; + + #[SettingsParameter(label: new TM("settings.behavior.part_info.extract_params_from_notes"))] + public bool $extractParamsFromNotes = true; +} diff --git a/src/Settings/BehaviorSettings/PartTableColumns.php b/src/Settings/BehaviorSettings/PartTableColumns.php new file mode 100644 index 000000000..c025c9529 --- /dev/null +++ b/src/Settings/BehaviorSettings/PartTableColumns.php @@ -0,0 +1,67 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\BehaviorSettings; + +use Symfony\Contracts\Translation\TranslatableInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +enum PartTableColumns : string implements TranslatableInterface +{ + + case NAME = "name"; + case ID = "id"; + case IPN = "ipn"; + case DESCRIPTION = "description"; + case CATEGORY = "category"; + case FOOTPRINT = "footprint"; + case MANUFACTURER = "manufacturer"; + case LOCATION = "storage_location"; + case AMOUNT = "amount"; + case MIN_AMOUNT = "minamount"; + case PART_UNIT = "partUnit"; + case ADDED_DATE = "addedDate"; + case LAST_MODIFIED = "lastModified"; + case NEEDS_REVIEW = "needs_review"; + case FAVORITE = "favorite"; + case MANUFACTURING_STATUS = "manufacturing_status"; + case MPN = "manufacturer_product_number"; + case CUSTOM_PART_STATE = 'partCustomState'; + case MASS = "mass"; + case TAGS = "tags"; + case ATTACHMENTS = "attachments"; + case EDIT = "edit"; + + public function trans(TranslatorInterface $translator, ?string $locale = null): string + { + $key = match($this) { + self::LOCATION => 'part.table.storeLocations', + self::NEEDS_REVIEW => 'part.table.needsReview', + self::MANUFACTURING_STATUS => 'part.table.manufacturingStatus', + self::MPN => 'part.table.mpn', + default => 'part.table.' . $this->value, + }; + + return $translator->trans($key, locale: $locale); + } +} diff --git a/src/Settings/BehaviorSettings/SidebarItems.php b/src/Settings/BehaviorSettings/SidebarItems.php new file mode 100644 index 000000000..cb0e60be8 --- /dev/null +++ b/src/Settings/BehaviorSettings/SidebarItems.php @@ -0,0 +1,53 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\BehaviorSettings; + +use Symfony\Contracts\Translation\TranslatableInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +enum SidebarItems: string implements TranslatableInterface +{ + case TOOLS = "tools"; + case CATEGORIES = "categories"; + case LOCATIONS = "locations"; + case FOOTPRINTS = "footprints"; + case MANUFACTURERS = "manufacturers"; + case SUPPLIERS = "suppliers"; + case PROJECTS = "projects"; + + public function trans(TranslatorInterface $translator, ?string $locale = null): string + { + $key = match($this) { + self::TOOLS => 'tools.label', + self::CATEGORIES => 'category.labelp', + self::LOCATIONS => 'storelocation.labelp', + self::FOOTPRINTS => 'footprint.labelp', + self::MANUFACTURERS => 'manufacturer.labelp', + self::SUPPLIERS => 'supplier.labelp', + self::PROJECTS => 'project.labelp', + }; + + return $translator->trans($key, locale: $locale); + } +} \ No newline at end of file diff --git a/src/Settings/BehaviorSettings/SidebarSettings.php b/src/Settings/BehaviorSettings/SidebarSettings.php new file mode 100644 index 000000000..a1ff6985e --- /dev/null +++ b/src/Settings/BehaviorSettings/SidebarSettings.php @@ -0,0 +1,83 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\BehaviorSettings; + +use App\Settings\SettingsIcon; +use Jbtronics\SettingsBundle\ParameterTypes\ArrayType; +use Jbtronics\SettingsBundle\ParameterTypes\EnumType; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsParameter; +use Jbtronics\SettingsBundle\Settings\SettingsTrait; +use Symfony\Component\Translation\TranslatableMessage as TM; +use Symfony\Component\Validator\Constraints as Assert; + +#[Settings(label: new TM("settings.behavior.sidebar"))] +#[SettingsIcon('fa-border-top-left')] +class SidebarSettings +{ + use SettingsTrait; + + + /** + * @var SidebarItems[] The items to show in the sidebar. + */ + #[SettingsParameter(ArrayType::class, + label: new TM("settings.behavior.sidebar.items"), + description: new TM("settings.behavior.sidebar.items.help"), + options: ['type' => EnumType::class, 'options' => ['class' => SidebarItems::class]], + formType: \Symfony\Component\Form\Extension\Core\Type\EnumType::class, + formOptions: ['class' => SidebarItems::class, 'multiple' => true, 'ordered' => true] + )] + #[Assert\NotBlank()] + #[Assert\Unique()] + public array $items = [SidebarItems::CATEGORIES, SidebarItems::PROJECTS, SidebarItems::TOOLS]; + + /** + * @var bool Whether categories, etc. should be grouped under a root node or put directly into the sidebar trees. + */ + #[SettingsParameter( + label: new TM("settings.behavior.sidebar.rootNodeEnabled"), + description: new TM("settings.behavior.sidebar.rootNodeEnabled.help") + )] + public bool $rootNodeEnabled = true; + + /** + * @var bool Whether the root node should be expanded by default, or not. + */ + #[SettingsParameter(label: new TM("settings.behavior.sidebar.rootNodeExpanded"))] + public bool $rootNodeExpanded = true; + + /** + * @var bool Whether the root node should redirect to a new entity creation page when clicked. + */ + #[SettingsParameter(label: new TM("settings.behavior.sidebar.rootNodeRedirectsToNewEntity"))] + public bool $rootNodeRedirectsToNewEntity = false; + + /** + * @var bool Whether to include child nodes in the data structure nodes table, or only show the selected node's parts. + */ + #[SettingsParameter(label: new TM("settings.behavior.sidebar.data_structure_nodes_table_include_children"), + description: new TM("settings.behavior.sidebar.data_structure_nodes_table_include_children.help"))] + public bool $dataStructureNodesTableIncludeChildren = true; +} diff --git a/src/Settings/BehaviorSettings/TableSettings.php b/src/Settings/BehaviorSettings/TableSettings.php new file mode 100644 index 000000000..b3421e41b --- /dev/null +++ b/src/Settings/BehaviorSettings/TableSettings.php @@ -0,0 +1,104 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\BehaviorSettings; + +use App\Settings\SettingsIcon; +use Jbtronics\SettingsBundle\Metadata\EnvVarMode; +use Jbtronics\SettingsBundle\ParameterTypes\ArrayType; +use Jbtronics\SettingsBundle\ParameterTypes\EnumType; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsParameter; +use Jbtronics\SettingsBundle\Settings\SettingsTrait; +use Symfony\Component\Translation\TranslatableMessage as TM; +use Symfony\Component\Validator\Constraints as Assert; + +#[Settings(label: new TM("settings.behavior.table"))] +#[SettingsIcon('fa-table')] +class TableSettings +{ + use SettingsTrait; + + #[SettingsParameter( + label: new TM("settings.behavior.table.default_page_size"), + description: new TM("settings.behavior.table.default_page_size.help"), + envVar: "int:TABLE_DEFAULT_PAGE_SIZE", + envVarMode: EnvVarMode::OVERWRITE, + )] + #[Assert\AtLeastOneOf(constraints: + [ + new Assert\Positive(), + new Assert\EqualTo(value: -1) + ] + )] + public int $fullDefaultPageSize = 50; + + + /** @var PartTableColumns[] */ + #[SettingsParameter(ArrayType::class, + label: new TM("settings.behavior.table.parts_default_columns"), + description: new TM("settings.behavior.table.parts_default_columns.help"), + options: ['type' => EnumType::class, 'options' => ['class' => PartTableColumns::class]], + formType: \Symfony\Component\Form\Extension\Core\Type\EnumType::class, + formOptions: ['class' => PartTableColumns::class, 'multiple' => true, 'ordered' => true], + envVar: "TABLE_PARTS_DEFAULT_COLUMNS", envVarMode: EnvVarMode::OVERWRITE, envVarMapper: [self::class, 'mapPartsDefaultColumnsEnv'] + )] + #[Assert\NotBlank()] + #[Assert\Unique()] + #[Assert\All([new Assert\Type(PartTableColumns::class)])] + public array $partsDefaultColumns = [PartTableColumns::NAME, PartTableColumns::DESCRIPTION, + PartTableColumns::CATEGORY, PartTableColumns::FOOTPRINT, PartTableColumns::MANUFACTURER, + PartTableColumns::LOCATION, PartTableColumns::AMOUNT, PartTableColumns::CUSTOM_PART_STATE]; + + #[SettingsParameter(label: new TM("settings.behavior.table.preview_image_min_width"), + formOptions: ['attr' => ['min' => 1, 'max' => 100]], + envVar: "int:TABLE_IMAGE_PREVIEW_MIN_SIZE", envVarMode: EnvVarMode::OVERWRITE + )] + #[Assert\Range(min: 1, max: 100)] + public int $previewImageMinWidth = 20; + + #[SettingsParameter(label: new TM("settings.behavior.table.preview_image_max_width"), + formOptions: ['attr' => ['min' => 1, 'max' => 100]], + envVar: "int:TABLE_IMAGE_PREVIEW_MAX_SIZE", envVarMode: EnvVarMode::OVERWRITE + )] + #[Assert\Range(min: 1, max: 100)] + #[Assert\GreaterThanOrEqual(propertyPath: 'previewImageMinWidth')] + public int $previewImageMaxWidth = 35; + + public static function mapPartsDefaultColumnsEnv(string $columns): array + { + $exploded = explode(',', $columns); + $ret = []; + foreach ($exploded as $column) { + $enum = PartTableColumns::tryFrom($column); + if (!$enum) { + throw new \InvalidArgumentException("Invalid column '$column' in TABLE_PARTS_DEFAULT_COLUMNS"); + } + + $ret[] = $enum; + } + + return $ret; + } + +} diff --git a/src/Settings/InfoProviderSystem/DigikeySettings.php b/src/Settings/InfoProviderSystem/DigikeySettings.php new file mode 100644 index 000000000..f42c1c1c3 --- /dev/null +++ b/src/Settings/InfoProviderSystem/DigikeySettings.php @@ -0,0 +1,73 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\InfoProviderSystem; + +use App\Form\Type\APIKeyType; +use App\Settings\SettingsIcon; +use Jbtronics\SettingsBundle\Metadata\EnvVarMode; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsTrait; +use Symfony\Component\Form\Extension\Core\Type\CountryType; +use Symfony\Component\Form\Extension\Core\Type\CurrencyType; +use Symfony\Component\Form\Extension\Core\Type\LanguageType; +use Symfony\Component\Translation\TranslatableMessage as TM; +use Jbtronics\SettingsBundle\Settings\SettingsParameter; +use Symfony\Component\Validator\Constraints as Assert; + +#[Settings(label: new TM("settings.ips.digikey"))] +#[SettingsIcon("fa-plug")] +class DigikeySettings +{ + use SettingsTrait; + + #[SettingsParameter( + label: new TM("settings.ips.digikey.client_id"), + formType: APIKeyType::class, + envVar: "PROVIDER_DIGIKEY_CLIENT_ID", envVarMode: EnvVarMode::OVERWRITE + )] + public ?string $clientId = null; + + #[SettingsParameter( + label: new TM("settings.ips.digikey.secret"), + formType: APIKeyType::class, + envVar: "PROVIDER_DIGIKEY_SECRET", envVarMode: EnvVarMode::OVERWRITE + )] + public ?string $secret = null; + + #[SettingsParameter(label: new TM("settings.ips.tme.currency"), formType: CurrencyType::class, + formOptions: ["preferred_choices" => ["EUR", "USD", "CHF", "GBP"]], + envVar: "PROVIDER_DIGIKEY_CURRENCY", envVarMode: EnvVarMode::OVERWRITE)] + #[Assert\Currency()] + public string $currency = "EUR"; + + #[SettingsParameter(label: new TM("settings.ips.tme.country"), formType: CountryType::class, + envVar: "PROVIDER_DIGIKEY_COUNTRY", envVarMode: EnvVarMode::OVERWRITE)] + #[Assert\Country] + public string $country = "DE"; + + #[SettingsParameter(label: new TM("settings.ips.tme.language"), formType: LanguageType::class, + envVar: "PROVIDER_DIGIKEY_LANGUAGE", envVarMode: EnvVarMode::OVERWRITE)] + #[Assert\Language] + public string $language = "en"; +} diff --git a/src/Settings/InfoProviderSystem/Element14Settings.php b/src/Settings/InfoProviderSystem/Element14Settings.php new file mode 100644 index 000000000..a4cdbf0d3 --- /dev/null +++ b/src/Settings/InfoProviderSystem/Element14Settings.php @@ -0,0 +1,48 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\InfoProviderSystem; + +use App\Form\Type\APIKeyType; +use App\Settings\SettingsIcon; +use Jbtronics\SettingsBundle\Metadata\EnvVarMode; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsParameter; +use Jbtronics\SettingsBundle\Settings\SettingsTrait; +use Symfony\Component\Translation\TranslatableMessage as TM; + +#[Settings(label: new TM("settings.ips.element14"))] +#[SettingsIcon("fa-plug")] +class Element14Settings +{ + use SettingsTrait; + + #[SettingsParameter(label: new TM("settings.ips.element14.apiKey"), description: new TM("settings.ips.element14.apiKey.help"),# + formType: APIKeyType::class, + formOptions: ["help_html" => true], envVar: "PROVIDER_ELEMENT14_KEY", envVarMode: EnvVarMode::OVERWRITE)] + public ?string $apiKey = null; + + #[SettingsParameter(label: new TM("settings.ips.element14.storeId"), description: new TM("settings.ips.element14.storeId.help"), + formOptions: ["help_html" => true], envVar: "PROVIDER_ELEMENT14_STORE_ID", envVarMode: EnvVarMode::OVERWRITE)] + public string $storeId = "de.farnell.com"; +} diff --git a/src/Settings/InfoProviderSystem/InfoProviderGeneralSettings.php b/src/Settings/InfoProviderSystem/InfoProviderGeneralSettings.php new file mode 100644 index 000000000..fac6aae9d --- /dev/null +++ b/src/Settings/InfoProviderSystem/InfoProviderGeneralSettings.php @@ -0,0 +1,45 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\InfoProviderSystem; + +use App\Form\InfoProviderSystem\ProviderSelectType; +use App\Settings\SettingsIcon; +use Jbtronics\SettingsBundle\ParameterTypes\ArrayType; +use Jbtronics\SettingsBundle\ParameterTypes\StringType; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsParameter; +use Symfony\Component\Translation\TranslatableMessage as TM; + +#[Settings(label: new TM("settings.ips.general"))] +#[SettingsIcon("fa-magnifying-glass")] +class InfoProviderGeneralSettings +{ + /** + * @var string[] + */ + #[SettingsParameter(type: ArrayType::class, label: new TM("settings.ips.default_providers"), + description: new TM("settings.ips.default_providers.help"), options: ['type' => StringType::class], + formType: ProviderSelectType::class, formOptions: ['input' => 'string', 'required' => false, 'empty_data' => []])] + public array $defaultSearchProviders = []; +} diff --git a/src/Settings/InfoProviderSystem/InfoProviderSettings.php b/src/Settings/InfoProviderSystem/InfoProviderSettings.php new file mode 100644 index 000000000..e6e258f5f --- /dev/null +++ b/src/Settings/InfoProviderSystem/InfoProviderSettings.php @@ -0,0 +1,66 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\InfoProviderSystem; + +use Jbtronics\SettingsBundle\Settings\EmbeddedSettings; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsParameter; +use Jbtronics\SettingsBundle\Settings\SettingsTrait; +use Symfony\Component\Translation\TranslatableMessage as TM; + +#[Settings(label: new TM("settings.ips"))] +class InfoProviderSettings +{ + use SettingsTrait; + + #[EmbeddedSettings] + public ?InfoProviderGeneralSettings $general = null; + + #[EmbeddedSettings] + public ?DigikeySettings $digikey = null; + + #[EmbeddedSettings] + public ?MouserSettings $mouser = null; + + #[EmbeddedSettings] + public ?TMESettings $tme = null; + + #[EmbeddedSettings] + public ?Element14Settings $element14 = null; + + #[EmbeddedSettings] + public ?OctopartSettings $octopartSettings = null; + + #[EmbeddedSettings] + public ?LCSCSettings $lcsc = null; + + #[EmbeddedSettings] + public ?OEMSecretsSettings $oemsecrets = null; + + #[EmbeddedSettings] + public ?ReicheltSettings $reichelt = null; + + #[EmbeddedSettings] + public ?PollinSettings $pollin = null; +} diff --git a/src/Settings/InfoProviderSystem/LCSCSettings.php b/src/Settings/InfoProviderSystem/LCSCSettings.php new file mode 100644 index 000000000..906838e29 --- /dev/null +++ b/src/Settings/InfoProviderSystem/LCSCSettings.php @@ -0,0 +1,49 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\InfoProviderSystem; + +use App\Settings\SettingsIcon; +use Jbtronics\SettingsBundle\Metadata\EnvVarMode; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsParameter; +use Jbtronics\SettingsBundle\Settings\SettingsTrait; +use Symfony\Component\Form\Extension\Core\Type\CurrencyType; +use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Translation\TranslatableMessage as TM; + +#[Settings(label: new TM("settings.ips.lcsc"), description: new TM("settings.ips.lcsc.help"))] +#[SettingsIcon("fa-plug")] +class LCSCSettings +{ + use SettingsTrait; + + #[SettingsParameter(label: new TM("settings.ips.lcsc.enabled"), + envVar: "bool:PROVIDER_LCSC_ENABLED", envVarMode: EnvVarMode::OVERWRITE)] + public bool $enabled = false; + + #[SettingsParameter(label: new TM("settings.ips.lcsc.currency"), formType: CurrencyType::class, + envVar: "string:PROVIDER_LCSC_CURRENCY", envVarMode: EnvVarMode::OVERWRITE)] + #[Assert\Currency()] + public string $currency = 'EUR'; +} \ No newline at end of file diff --git a/src/Settings/InfoProviderSystem/MouserSearchOptions.php b/src/Settings/InfoProviderSystem/MouserSearchOptions.php new file mode 100644 index 000000000..429fab567 --- /dev/null +++ b/src/Settings/InfoProviderSystem/MouserSearchOptions.php @@ -0,0 +1,47 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\InfoProviderSystem; + +use Symfony\Contracts\Translation\TranslatableInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +enum MouserSearchOptions: string implements TranslatableInterface +{ + case NONE = "None"; + case ROHS = "Rohs"; + case IN_STOCK = "InStock"; + case ROHS_AND_INSTOCK = "RohsAndInStock"; + + public function trans(TranslatorInterface $translator, ?string $locale = null): string + { + $key = match($this) { + self::NONE => "settings.ips.mouser.searchOptions.none", + self::ROHS => "settings.ips.mouser.searchOptions.rohs", + self::IN_STOCK => "settings.ips.mouser.searchOptions.inStock", + self::ROHS_AND_INSTOCK => "settings.ips.mouser.searchOptions.rohsAndInStock", + }; + + return $translator->trans($key, locale: $locale); + } +} \ No newline at end of file diff --git a/src/Settings/InfoProviderSystem/MouserSettings.php b/src/Settings/InfoProviderSystem/MouserSettings.php new file mode 100644 index 000000000..0abaa7f2e --- /dev/null +++ b/src/Settings/InfoProviderSystem/MouserSettings.php @@ -0,0 +1,69 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\InfoProviderSystem; + +use App\Form\Type\APIKeyType; +use App\Settings\SettingsIcon; +use Jbtronics\SettingsBundle\Metadata\EnvVarMode; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsParameter; +use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Translation\TranslatableMessage as TM; + +#[Settings(label: new TM("settings.ips.mouser"))] +#[SettingsIcon("fa-plug")] +class MouserSettings +{ + #[SettingsParameter(label: new TM("settings.ips.mouser.apiKey"), description: new TM("settings.ips.mouser.apiKey.help"), + formType: APIKeyType::class, + formOptions: ["help_html" => true], envVar: "PROVIDER_MOUSER_KEY", envVarMode: EnvVarMode::OVERWRITE)] + public ?string $apiKey = null; + + /** @var int The number of results to get from Mouser while searching (please note that this value is max 50) */ + #[SettingsParameter(label: new TM("settings.ips.mouser.searchLimit"), description: new TM("settings.ips.mouser.searchLimit.help"), + envVar: "int:PROVIDER_MOUSER_SEARCH_LIMIT", envVarMode: EnvVarMode::OVERWRITE)] + #[Assert\Range(min: 1, max: 50)] + public int $searchLimit = 50; + + /** @var MouserSearchOptions Filter search results by RoHS compliance and stock availability */ + #[SettingsParameter(label: new TM("settings.ips.mouser.searchOptions"), description: new TM("settings.ips.mouser.searchOptions.help"), + envVar: "PROVIDER_MOUSER_SEARCH_OPTION", envVarMode: EnvVarMode::OVERWRITE, envVarMapper: [self::class, "mapSearchOptionEnvVar"])] + public MouserSearchOptions $searchOption = MouserSearchOptions::NONE; + + /** @var bool It is recommended to leave this set to 'true'. The option is not really documented by Mouser: + * Used when searching for keywords in the language specified when you signed up for Search API. */ + //TODO: Put this into some expert mode only + //#[SettingsParameter(envVar: "bool:PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE")] + public bool $searchWithSignUpLanguage = true; + + public static function mapSearchOptionEnvVar(?string $value): MouserSearchOptions + { + if (!$value) { + return MouserSearchOptions::NONE; + } + + return MouserSearchOptions::tryFrom($value) ?? MouserSearchOptions::NONE; + } + +} diff --git a/src/Settings/InfoProviderSystem/OEMSecretsSettings.php b/src/Settings/InfoProviderSystem/OEMSecretsSettings.php new file mode 100644 index 000000000..77cf9080c --- /dev/null +++ b/src/Settings/InfoProviderSystem/OEMSecretsSettings.php @@ -0,0 +1,90 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\InfoProviderSystem; + +use App\Form\Type\APIKeyType; +use App\Settings\SettingsIcon; +use Jbtronics\SettingsBundle\Metadata\EnvVarMode; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsParameter; +use Jbtronics\SettingsBundle\Settings\SettingsTrait; +use Symfony\Component\Form\Extension\Core\Type\CountryType; +use Symfony\Component\Form\Extension\Core\Type\CurrencyType; +use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Translation\TranslatableMessage as TM; + +#[Settings(label: new TM("settings.ips.oemsecrets"))] +#[SettingsIcon("fa-plug")] +class OEMSecretsSettings +{ + use SettingsTrait; + + public const SUPPORTED_CURRENCIES = ["AUD", "CAD", "CHF", "CNY", "DKK", "EUR", "GBP", "HKD", "ILS", "INR", "JPY", "KRW", "NOK", + "NZD", "RUB", "SEK", "SGD", "TWD", "USD"]; + + #[SettingsParameter(label: new TM("settings.ips.element14.apiKey"), + formType: APIKeyType::class, + envVar: "PROVIDER_OEMSECRETS_KEY", envVarMode: EnvVarMode::OVERWRITE)] + public ?string $apiKey = null; + + #[Assert\Country] + #[SettingsParameter(label: new TM("settings.ips.tme.country"), formType: CountryType::class, formOptions: ["preferred_choices" => ["DE", "PL", "GB", "FR", "US"]], + envVar: "PROVIDER_OEMSECRETS_COUNTRY_CODE", envVarMode: EnvVarMode::OVERWRITE)] + public ?string $country = "DE"; + + #[SettingsParameter(label: new TM("settings.ips.tme.currency"), formType: CurrencyType::class, formOptions: ["preferred_choices" => self::SUPPORTED_CURRENCIES], + envVar: "PROVIDER_OEMSECRETS_CURRENCY", envVarMode: EnvVarMode::OVERWRITE)] + #[Assert\Choice(choices: self::SUPPORTED_CURRENCIES)] + public string $currency = "EUR"; + + /** + * @var bool If this is enabled, distributors with zero prices + * will be discarded from the creation of a new part + */ + #[SettingsParameter(label: new TM("settings.ips.oemsecrets.keepZeroPrices"), description: new TM("settings.ips.oemsecrets.keepZeroPrices.help"), + envVar: "bool:PROVIDER_OEMSECRETS_ZERO_PRICE", envVarMode: EnvVarMode::OVERWRITE)] + public bool $keepZeroPrices = false; + + /** + * @var bool If 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" + */ + #[SettingsParameter(label: new TM("settings.ips.oemsecrets.parseParams"), description: new TM("settings.ips.oemsecrets.parseParams.help"), + envVar: "bool:PROVIDER_OEMSECRETS_SET_PARAM", envVarMode: EnvVarMode::OVERWRITE)] + public bool $parseParams = true; + + #[SettingsParameter(label: new TM("settings.ips.oemsecrets.sortMode"), envVar: "PROVIDER_OEMSECRETS_SORT_CRITERIA", envVarMapper: [self::class, "mapSortModeEnvVar"])] + public OEMSecretsSortMode $sortMode = OEMSecretsSortMode::COMPLETENESS; + + + public static function mapSortModeEnvVar(?string $value): OEMSecretsSortMode + { + if (!$value) { + return OEMSecretsSortMode::NONE; + } + + return OEMSecretsSortMode::tryFrom($value) ?? OEMSecretsSortMode::NONE; + } +} diff --git a/src/Settings/InfoProviderSystem/OEMSecretsSortMode.php b/src/Settings/InfoProviderSystem/OEMSecretsSortMode.php new file mode 100644 index 000000000..e479e07e8 --- /dev/null +++ b/src/Settings/InfoProviderSystem/OEMSecretsSortMode.php @@ -0,0 +1,46 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\InfoProviderSystem; + +use Symfony\Contracts\Translation\TranslatableInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * 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. + */ +enum OEMSecretsSortMode : string implements TranslatableInterface +{ + case NONE = "N"; + case COMPLETENESS = "C"; + case MANUFACTURER = "M"; + + public function trans(TranslatorInterface $translator, ?string $locale = null): string + { + return $translator->trans('settings.ips.oemsecrets.sortMode.' . $this->value, locale: $locale); + } +} \ No newline at end of file diff --git a/src/Settings/InfoProviderSystem/OctopartSettings.php b/src/Settings/InfoProviderSystem/OctopartSettings.php new file mode 100644 index 000000000..c28da4592 --- /dev/null +++ b/src/Settings/InfoProviderSystem/OctopartSettings.php @@ -0,0 +1,81 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\InfoProviderSystem; + +use App\Form\Type\APIKeyType; +use App\Settings\SettingsIcon; +use Jbtronics\SettingsBundle\Metadata\EnvVarMode; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsParameter; +use Jbtronics\SettingsBundle\Settings\SettingsTrait; +use Symfony\Component\Form\Extension\Core\Type\CountryType; +use Symfony\Component\Form\Extension\Core\Type\CurrencyType; +use Symfony\Component\Form\Extension\Core\Type\NumberType; +use Symfony\Component\Translation\TranslatableMessage as TM; +use Symfony\Component\Validator\Constraints as Assert; + +#[Settings(label: new TM("settings.ips.octopart"))] +#[SettingsIcon("fa-plug")] +class OctopartSettings +{ + use SettingsTrait; + + #[SettingsParameter( + label: new TM("settings.ips.digikey.client_id"), + formType: APIKeyType::class, + envVar: "PROVIDER_OCTOPART_CLIENT_ID", envVarMode: EnvVarMode::OVERWRITE, + )] + public ?string $clientId = null; + + #[SettingsParameter( + label: new TM("settings.ips.digikey.secret"), + formType: APIKeyType::class, + envVar: "PROVIDER_OCTOPART_SECRET", envVarMode: EnvVarMode::OVERWRITE + )] + public ?string $secret = null; + + #[SettingsParameter(label: new TM("settings.ips.tme.currency"), formType: CurrencyType::class, + formOptions: ["preferred_choices" => ["EUR", "USD", "CHF", "GBP"]], + envVar: "PROVIDER_OCTOPART_CURRENCY", envVarMode: EnvVarMode::OVERWRITE)] + #[Assert\Currency()] + public string $currency = "EUR"; + + #[SettingsParameter(label: new TM("settings.ips.tme.country"), formType: CountryType::class, + envVar: "PROVIDER_OCTOPART_COUNTRY", envVarMode: EnvVarMode::OVERWRITE)] + #[Assert\Country] + public string $country = "DE"; + + #[SettingsParameter(label: new TM("settings.ips.octopart.searchLimit"), description: new TM("settings.ips.octopart.searchLimit.help"), + formType: NumberType::class, formOptions: ["attr" => ["min" => 1, "max" => 100]], + envVar: "int:PROVIDER_OCTOPART_SEARCH_LIMIT", envVarMode: EnvVarMode::OVERWRITE)] + #[Assert\Range(min: 1, max: 100)] + public int $searchLimit = 10; + + #[SettingsParameter(label: new TM("settings.ips.octopart.onlyAuthorizedSellers"), + description: new TM("settings.ips.octopart.onlyAuthorizedSellers.help"), + envVar: "bool:PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS", envVarMode: EnvVarMode::OVERWRITE + )] + public bool $onlyAuthorizedSellers = true; + +} diff --git a/src/Settings/InfoProviderSystem/PollinSettings.php b/src/Settings/InfoProviderSystem/PollinSettings.php new file mode 100644 index 000000000..033d8b7e6 --- /dev/null +++ b/src/Settings/InfoProviderSystem/PollinSettings.php @@ -0,0 +1,39 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\InfoProviderSystem; + +use App\Settings\SettingsIcon; +use Jbtronics\SettingsBundle\Metadata\EnvVarMode; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsParameter; +use Symfony\Component\Translation\TranslatableMessage as TM; + +#[Settings(label: new TM("settings.ips.pollin"), description: new TM("settings.ips.pollin.help"))] +#[SettingsIcon("fa-plug")] +class PollinSettings +{ + #[SettingsParameter(label: new TM("settings.ips.lcsc.enabled"), + envVar: "bool:PROVIDER_POLLIN_ENABLED", envVarMode: EnvVarMode::OVERWRITE)] + public bool $enabled = false; +} \ No newline at end of file diff --git a/src/Settings/InfoProviderSystem/ReicheltSettings.php b/src/Settings/InfoProviderSystem/ReicheltSettings.php new file mode 100644 index 000000000..588447de2 --- /dev/null +++ b/src/Settings/InfoProviderSystem/ReicheltSettings.php @@ -0,0 +1,68 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\InfoProviderSystem; + +use App\Settings\SettingsIcon; +use Jbtronics\SettingsBundle\Metadata\EnvVarMode; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsParameter; +use Jbtronics\SettingsBundle\Settings\SettingsTrait; +use Symfony\Component\Form\Extension\Core\Type\CountryType; +use Symfony\Component\Form\Extension\Core\Type\CurrencyType; +use Symfony\Component\Form\Extension\Core\Type\LanguageType; +use Symfony\Component\Translation\TranslatableMessage as TM; +use Symfony\Component\Validator\Constraints as Assert; + +#[Settings(label: new TM("settings.ips.reichelt"), description: new TM("settings.ips.reichelt.help"))] +#[SettingsIcon("fa-plug")] +class ReicheltSettings +{ + use SettingsTrait; + + public const SUPPORTED_LANGUAGE = ["en", "de", "fr", "nl", "pl", "it", "es"]; + + #[SettingsParameter(label: new TM("settings.ips.lcsc.enabled"), + envVar: "bool:PROVIDER_REICHELT_ENABLED", envVarMode: EnvVarMode::OVERWRITE)] + public bool $enabled = false; + + #[SettingsParameter(label: new TM("settings.ips.tme.currency"), formType: CurrencyType::class, formOptions: ["preferred_choices" => ["EUR"]], + envVar: "PROVIDER_REICHELT_CURRENCY", envVarMode: EnvVarMode::OVERWRITE)] + public string $currency = "EUR"; + + #[SettingsParameter(label: new TM("settings.ips.tme.language"), formType: LanguageType::class, formOptions: ["preferred_choices" => self::SUPPORTED_LANGUAGE], + envVar: "PROVIDER_REICHELT_LANGUAGE", envVarMode: EnvVarMode::OVERWRITE)] + #[Assert\Language()] + #[Assert\Choice(choices: self::SUPPORTED_LANGUAGE)] + public string $language = "en"; + + #[SettingsParameter(label: new TM("settings.ips.tme.country"), formType: CountryType::class, formOptions: ["preferred_choices" => ["DE", "PL", "GB", "FR"]], + envVar: "PROVIDER_REICHELT_COUNTRY", envVarMode: EnvVarMode::OVERWRITE)] + #[Assert\Country] + public string $country = "DE"; + + #[SettingsParameter(label: new TM("settings.ips.reichelt.include_vat"), + envVar: "bool:PROVIDER_REICHELT_INCLUDE_VAT", envVarMode: EnvVarMode::OVERWRITE)] + public bool $includeVAT = true; + +} \ No newline at end of file diff --git a/src/Settings/InfoProviderSystem/TMESettings.php b/src/Settings/InfoProviderSystem/TMESettings.php new file mode 100644 index 000000000..d6f03d341 --- /dev/null +++ b/src/Settings/InfoProviderSystem/TMESettings.php @@ -0,0 +1,75 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\InfoProviderSystem; + +use App\Form\Type\APIKeyType; +use App\Settings\SettingsIcon; +use Jbtronics\SettingsBundle\Metadata\EnvVarMode; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsParameter; +use Jbtronics\SettingsBundle\Settings\SettingsTrait; +use Symfony\Component\Form\Extension\Core\Type\CountryType; +use Symfony\Component\Form\Extension\Core\Type\CurrencyType; +use Symfony\Component\Form\Extension\Core\Type\LanguageType; +use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Translation\TranslatableMessage as TM; + +#[Settings(label: new TM("settings.ips.tme"))] +#[SettingsIcon("fa-plug")] +class TMESettings +{ + use SettingsTrait; + + private const SUPPORTED_CURRENCIES = ["EUR", "USD", "PLN", "GBP"]; + + #[SettingsParameter(label: new TM("settings.ips.tme.token"), + description: new TM("settings.ips.tme.token.help"), + formType: APIKeyType::class, formOptions: ["help_html" => true], + envVar: "PROVIDER_TME_KEY", envVarMode: EnvVarMode::OVERWRITE)] + public ?string $apiToken = null; + + #[SettingsParameter(label: new TM("settings.ips.tme.secret"), + formType: APIKeyType::class, + envVar: "PROVIDER_TME_SECRET", envVarMode: EnvVarMode::OVERWRITE)] + public ?string $apiSecret = null; + + #[SettingsParameter(label: new TM("settings.ips.tme.currency"), formType: CurrencyType::class, formOptions: ["preferred_choices" => self::SUPPORTED_CURRENCIES], + envVar: "PROVIDER_TME_CURRENCY", envVarMode: EnvVarMode::OVERWRITE)] + #[Assert\Choice(choices: self::SUPPORTED_CURRENCIES)] + public string $currency = "EUR"; + + #[SettingsParameter(label: new TM("settings.ips.tme.language"), formType: LanguageType::class, formOptions: ["preferred_choices" => ["en", "de", "fr", "pl"]], + envVar: "PROVIDER_TME_LANGUAGE", envVarMode: EnvVarMode::OVERWRITE)] + #[Assert\Language] + public string $language = "en"; + + #[SettingsParameter(label: new TM("settings.ips.tme.country"), formType: CountryType::class, formOptions: ["preferred_choices" => ["DE", "PL", "GB", "FR"]], + envVar: "PROVIDER_TME_COUNTRY", envVarMode: EnvVarMode::OVERWRITE)] + #[Assert\Country] + public string $country = "DE"; + + #[SettingsParameter(label: new TM("settings.ips.tme.grossPrices"), + envVar: "bool:PROVIDER_TME_GET_GROSS_PRICES", envVarMode: EnvVarMode::OVERWRITE)] + public bool $grossPrices = true; +} diff --git a/src/Settings/MiscSettings/ExchangeRateSettings.php b/src/Settings/MiscSettings/ExchangeRateSettings.php new file mode 100644 index 000000000..744523c6b --- /dev/null +++ b/src/Settings/MiscSettings/ExchangeRateSettings.php @@ -0,0 +1,43 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\MiscSettings; + +use App\Form\Type\APIKeyType; +use App\Settings\SettingsIcon; +use Jbtronics\SettingsBundle\Metadata\EnvVarMode; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsParameter; +use Symfony\Component\Translation\TranslatableMessage as TM; + +#[Settings(name: "exchange_rate", label: new TM("settings.misc.exchange_rate"))] +#[SettingsIcon("fa-money-bill-transfer")] +class ExchangeRateSettings +{ + #[SettingsParameter(label: new TM("settings.misc.exchange_rate.fixer_api_key"), + description: new TM("settings.misc.exchange_rate.fixer_api_key.help"), + formType: APIKeyType::class, + envVar: "FIXER_API_KEY", envVarMode: EnvVarMode::OVERWRITE, + )] + public ?string $fixerApiKey = null; +} diff --git a/src/Settings/MiscSettings/KiCadEDASettings.php b/src/Settings/MiscSettings/KiCadEDASettings.php new file mode 100644 index 000000000..d8f1026d3 --- /dev/null +++ b/src/Settings/MiscSettings/KiCadEDASettings.php @@ -0,0 +1,46 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\MiscSettings; + +use App\Settings\SettingsIcon; +use Jbtronics\SettingsBundle\Metadata\EnvVarMode; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsParameter; +use Jbtronics\SettingsBundle\Settings\SettingsTrait; +use Symfony\Component\Translation\TranslatableMessage as TM; +use Symfony\Component\Validator\Constraints as Assert; + +#[Settings(label: new TM("settings.misc.kicad_eda"))] +#[SettingsIcon("fa-bolt-lightning")] +class KiCadEDASettings +{ + use SettingsTrait; + + + #[SettingsParameter(label: new TM("settings.misc.kicad_eda.category_depth"), + description: new TM("settings.misc.kicad_eda.category_depth.help"), + envVar: "int:EDA_KICAD_CATEGORY_DEPTH", envVarMode: EnvVarMode::OVERWRITE)] + #[Assert\Range(min: -1)] + public int $categoryDepth = 0; +} \ No newline at end of file diff --git a/src/Settings/MiscSettings/MiscSettings.php b/src/Settings/MiscSettings/MiscSettings.php new file mode 100644 index 000000000..1c304d4a0 --- /dev/null +++ b/src/Settings/MiscSettings/MiscSettings.php @@ -0,0 +1,38 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\MiscSettings; + +use Jbtronics\SettingsBundle\Settings\EmbeddedSettings; +use Jbtronics\SettingsBundle\Settings\Settings; +use Symfony\Component\Translation\TranslatableMessage as TM; + +#[Settings(label: new TM("settings.misc"))] +class MiscSettings +{ + #[EmbeddedSettings] + public ?KiCadEDASettings $kicadEDA = null; + + #[EmbeddedSettings] + public ?ExchangeRateSettings $exchangeRate = null; +} diff --git a/src/Settings/SettingsIcon.php b/src/Settings/SettingsIcon.php new file mode 100644 index 000000000..45bfc544c --- /dev/null +++ b/src/Settings/SettingsIcon.php @@ -0,0 +1,32 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings; + +#[\Attribute(\Attribute::TARGET_CLASS)] +class SettingsIcon +{ + public function __construct(public string $icon) + { + } +} \ No newline at end of file diff --git a/src/Settings/SystemSettings/AttachmentsSettings.php b/src/Settings/SystemSettings/AttachmentsSettings.php new file mode 100644 index 000000000..6d15c639c --- /dev/null +++ b/src/Settings/SystemSettings/AttachmentsSettings.php @@ -0,0 +1,61 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\SystemSettings; + +use App\Settings\SettingsIcon; +use Jbtronics\SettingsBundle\Metadata\EnvVarMode; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsParameter; +use Jbtronics\SettingsBundle\Settings\SettingsTrait; +use Symfony\Component\Translation\TranslatableMessage as TM; +use Symfony\Component\Validator\Constraints as Assert; + +#[Settings(label: new TM("settings.system.attachments"))] +#[SettingsIcon("fa-paperclip")] +class AttachmentsSettings +{ + use SettingsTrait; + + #[SettingsParameter( + label: new TM("settings.system.attachments.maxFileSize"), + description: new TM("settings.system.attachments.maxFileSize.help"), + envVar: "MAX_ATTACHMENT_FILE_SIZE", envVarMode: EnvVarMode::OVERWRITE + )] + #[Assert\Regex("/^([1-9][0-9]*)([KMG])?$/", message: "validator.fileSize.invalidFormat")] + public string $maxFileSize = '100M'; + + #[SettingsParameter( + label: new TM("settings.system.attachments.allowDownloads"), + description: new TM("settings.system.attachments.allowDownloads.help"), + formOptions: ['help_html' => true], + envVar: "bool:ALLOW_ATTACHMENT_DOWNLOADS", envVarMode: EnvVarMode::OVERWRITE + )] + public bool $allowDownloads = false; + + #[SettingsParameter( + label: new TM("settings.system.attachments.downloadByDefault"), + envVar: "bool:ATTACHMENT_DOWNLOAD_BY_DEFAULT", envVarMode: EnvVarMode::OVERWRITE + )] + public bool $downloadByDefault = false; +} \ No newline at end of file diff --git a/src/Settings/SystemSettings/CustomizationSettings.php b/src/Settings/SystemSettings/CustomizationSettings.php new file mode 100644 index 000000000..623e61870 --- /dev/null +++ b/src/Settings/SystemSettings/CustomizationSettings.php @@ -0,0 +1,84 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\SystemSettings; + +use App\Form\Type\RichTextEditorType; +use App\Form\Type\ThemeChoiceType; +use App\Settings\SettingsIcon; +use App\Validator\Constraints\ValidTheme; +use Jbtronics\SettingsBundle\Metadata\EnvVarMode; +use Jbtronics\SettingsBundle\ParameterTypes\ArrayType; +use Jbtronics\SettingsBundle\ParameterTypes\EnumType; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsParameter; +use Jbtronics\SettingsBundle\Settings\SettingsTrait; +use Symfony\Component\Translation\TranslatableMessage as TM; +use Symfony\Component\Validator\Constraints as Assert; + +#[Settings(name: "customization", label: new TM("settings.system.customization"))] +#[SettingsIcon("fa-paint-roller")] +class CustomizationSettings +{ + use SettingsTrait; + + #[SettingsParameter( + label: new TM("settings.system.customization.instanceName"), + description: new TM("settings.system.customization.instanceName.help"), + envVar: "INSTANCE_NAME", envVarMode: EnvVarMode::OVERWRITE, + )] + public string $instanceName = "Part-DB"; + + #[SettingsParameter( + label: new TM("settings.system.customization.theme"), + formType: ThemeChoiceType::class, formOptions: ['placeholder' => false] + )] + #[ValidTheme] + public string $theme = 'bootstrap'; + + #[SettingsParameter( + label: new TM("settings.system.customization.banner"), + formType: RichTextEditorType::class, formOptions: ['mode' => 'markdown-full'], + envVar: "BANNER", envVarMode: EnvVarMode::OVERWRITE, + )] + public ?string $banner = null; + + /** + * @var HomepageItems[] The items to show in the sidebar. + */ + #[SettingsParameter(ArrayType::class, + label: new TM("settings.behavior.hompepage.items"), + description: new TM("settings.behavior.homepage.items.help"), + options: ['type' => EnumType::class, 'options' => ['class' => HomepageItems::class]], + formType: \Symfony\Component\Form\Extension\Core\Type\EnumType::class, + formOptions: ['class' => HomepageItems::class, 'multiple' => true, 'ordered' => true] + )] + #[Assert\NotBlank()] + #[Assert\Unique()] + public array $homepageitems = [HomepageItems::SEARCH, HomepageItems::BANNER, HomepageItems::FIRST_STEPS, HomepageItems::LICENSE, HomepageItems::LAST_ACTIVITY]; + + #[SettingsParameter( + label: new TM("settings.system.customization.showVersionOnHomepage") + )] + public bool $showVersionOnHomepage = true; +} diff --git a/src/Settings/SystemSettings/HistorySettings.php b/src/Settings/SystemSettings/HistorySettings.php new file mode 100644 index 000000000..46003c6d9 --- /dev/null +++ b/src/Settings/SystemSettings/HistorySettings.php @@ -0,0 +1,87 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\SystemSettings; + +use App\Form\History\EnforceEventCommentTypesType; +use App\Services\LogSystem\EventCommentType; +use App\Settings\SettingsIcon; +use Jbtronics\SettingsBundle\Metadata\EnvVarMode; +use Jbtronics\SettingsBundle\ParameterTypes\ArrayType; +use Jbtronics\SettingsBundle\ParameterTypes\EnumType; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsParameter; +use Jbtronics\SettingsBundle\Settings\SettingsTrait; +use Symfony\Component\Translation\TranslatableMessage as TM; + +#[Settings(label: new TM("settings.system.history"))] +#[SettingsIcon("fa-binoculars")] +class HistorySettings +{ + use SettingsTrait; + + #[SettingsParameter( + label: new TM("settings.system.history.saveChangedFields"), + envVar: "bool:HISTORY_SAVE_CHANGED_FIELDS", envVarMode: EnvVarMode::OVERWRITE)] + public bool $saveChangedFields = true; + + #[SettingsParameter( + label: new TM("settings.system.history.saveOldData"), + envVar: "bool:HISTORY_SAVE_CHANGED_DATA", envVarMode: EnvVarMode::OVERWRITE + )] + public bool $saveOldData = true; + + #[SettingsParameter( + label: new TM("settings.system.history.saveNewData"), + envVar: "bool:HISTORY_SAVE_NEW_DATA", envVarMode: EnvVarMode::OVERWRITE + )] + public bool $saveNewData = true; + + #[SettingsParameter( + label: new TM("settings.system.history.saveRemovedData"), + envVar: "bool:HISTORY_SAVE_REMOVED_DATA", envVarMode: EnvVarMode::OVERWRITE + )] + public bool $saveRemovedData = true; + + /** @var EventCommentType[] */ + #[SettingsParameter( + type: ArrayType::class, + label: new TM("settings.system.history.enforceComments"), + description: new TM("settings.system.history.enforceComments.description"), + options: ['type' => EnumType::class, 'nullable' => false, 'options' => ['class' => EventCommentType::class]], + formType: EnforceEventCommentTypesType::class, + formOptions: ['required' => false, "empty_data" => []], + envVar: "ENFORCE_CHANGE_COMMENTS_FOR", envVarMode: EnvVarMode::OVERWRITE, envVarMapper: [self::class, 'mapEnforceComments'] + )] + public array $enforceComments = []; + + public static function mapEnforceComments(string $value): array + { + if (trim($value) === '') { + return []; + } + + $explode = explode(',', $value); + return array_map(fn(string $type) => EventCommentType::from($type), $explode); + } +} \ No newline at end of file diff --git a/src/Settings/SystemSettings/HomepageItems.php b/src/Settings/SystemSettings/HomepageItems.php new file mode 100644 index 000000000..7366dfa24 --- /dev/null +++ b/src/Settings/SystemSettings/HomepageItems.php @@ -0,0 +1,51 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\SystemSettings; + +use Symfony\Contracts\Translation\TranslatableInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +use function Symfony\Component\Translation\t; + +enum HomepageItems: string implements TranslatableInterface +{ + case SEARCH = 'search'; + case BANNER = 'banner'; + case LICENSE = 'license'; + case FIRST_STEPS = 'first_steps'; + case LAST_ACTIVITY = 'last_activity'; + + public function trans(TranslatorInterface $translator, ?string $locale = null): string + { + $key = match($this) { + self::SEARCH => 'search.placeholder', + self::BANNER => 'settings.system.customization.banner', + self::LICENSE => 'homepage.license', + self::FIRST_STEPS => 'homepage.first_steps.title', + self::LAST_ACTIVITY => 'homepage.last_activity', + }; + + return $translator->trans($key, locale: $locale); + } +} diff --git a/src/Settings/SystemSettings/LocalizationSettings.php b/src/Settings/SystemSettings/LocalizationSettings.php new file mode 100644 index 000000000..7c83d1ef2 --- /dev/null +++ b/src/Settings/SystemSettings/LocalizationSettings.php @@ -0,0 +1,76 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\SystemSettings; + +use App\Form\Type\LanguageMenuEntriesType; +use App\Form\Type\LocaleSelectType; +use App\Settings\SettingsIcon; +use Jbtronics\SettingsBundle\Metadata\EnvVarMode; +use Jbtronics\SettingsBundle\ParameterTypes\ArrayType; +use Jbtronics\SettingsBundle\ParameterTypes\StringType; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsParameter; +use Jbtronics\SettingsBundle\Settings\SettingsTrait; +use Symfony\Component\Form\Extension\Core\Type\CurrencyType; +use Symfony\Component\Form\Extension\Core\Type\TimezoneType; +use Symfony\Component\Translation\TranslatableMessage as TM; +use Symfony\Component\Validator\Constraints as Assert; + +#[Settings(label: new TM("settings.system.localization"))] +#[SettingsIcon("fa-globe")] +class LocalizationSettings +{ + use SettingsTrait; + + #[Assert\Locale()] + #[Assert\NotBlank()] + #[SettingsParameter(label: new TM("settings.system.localization.locale"), formType: LocaleSelectType::class, + envVar: "string:DEFAULT_LANG", envVarMode: EnvVarMode::OVERWRITE)] + public string $locale = 'en'; + + #[Assert\Timezone()] + #[Assert\NotBlank()] + #[SettingsParameter(label: new TM("settings.system.localization.timezone"), formType: TimezoneType::class, + envVar: "string:DEFAULT_TIMEZONE", envVarMode: EnvVarMode::OVERWRITE)] + public string $timezone = 'Europe/Berlin'; + + #[Assert\Currency()] + #[Assert\NotBlank()] + #[SettingsParameter(label: new TM("settings.system.localization.base_currency"), + description: new TM("settings.system.localization.base_currency_description"), + formType: CurrencyType::class, formOptions: ['preferred_choices' => ['EUR', 'USD', 'GBP', "JPY", "CNY"], 'help_html' => true], + envVar: "string:BASE_CURRENCY", envVarMode: EnvVarMode::OVERWRITE + )] + public string $baseCurrency = 'EUR'; + + #[SettingsParameter(type: ArrayType::class, + label: new TM("settings.system.localization.language_menu_entries"), + description: new TM("settings.system.localization.language_menu_entries.description"), + options: ['type' => StringType::class], + formType: LanguageMenuEntriesType::class, + formOptions: ['multiple' => true, 'required' => false, 'ordered' => true], + )] + #[Assert\All([new Assert\Locale()])] + public array $languageMenuEntries = []; +} diff --git a/src/Settings/SystemSettings/PrivacySettings.php b/src/Settings/SystemSettings/PrivacySettings.php new file mode 100644 index 000000000..1ef3c635c --- /dev/null +++ b/src/Settings/SystemSettings/PrivacySettings.php @@ -0,0 +1,53 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\SystemSettings; + +use App\Settings\SettingsIcon; +use Jbtronics\SettingsBundle\Metadata\EnvVarMode; +use Jbtronics\SettingsBundle\Settings\Settings; +use Jbtronics\SettingsBundle\Settings\SettingsParameter; +use Jbtronics\SettingsBundle\Settings\SettingsTrait; +use Symfony\Component\Translation\TranslatableMessage as TM; + +#[Settings(label: new TM("settings.system.privacy"))] +#[SettingsIcon("fa-location-pin-lock")] +class PrivacySettings +{ + use SettingsTrait; + + #[SettingsParameter( + label: new TM("settings.system.privacy.checkForUpdates"), + description: new TM("settings.system.privacy.checkForUpdates.description"), + envVar: 'bool:CHECK_FOR_UPDATES', envVarMode: EnvVarMode::OVERWRITE)] + public bool $checkForUpdates = true; + + /** + * @var bool Use gravatars for user avatars, when user has no own avatar defined + */ + #[SettingsParameter( + label: new TM("settings.system.privacy.useGravatar"), + description: new TM("settings.system.privacy.useGravatar.description"), + envVar: 'bool:USE_GRAVATAR', envVarMode: EnvVarMode::OVERWRITE)] + public bool $useGravatar = false; +} \ No newline at end of file diff --git a/src/Settings/SystemSettings/SystemSettings.php b/src/Settings/SystemSettings/SystemSettings.php new file mode 100644 index 000000000..71dd963d5 --- /dev/null +++ b/src/Settings/SystemSettings/SystemSettings.php @@ -0,0 +1,47 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Settings\SystemSettings; + +use Jbtronics\SettingsBundle\Settings\EmbeddedSettings; +use Jbtronics\SettingsBundle\Settings\Settings; +use Symfony\Component\Translation\TranslatableMessage as TM; + +#[Settings(label: new TM("settings.system"))] +class SystemSettings +{ + #[EmbeddedSettings()] + public ?LocalizationSettings $localization = null; + + #[EmbeddedSettings()] + public ?CustomizationSettings $customization = null; + + #[EmbeddedSettings()] + public ?PrivacySettings $privacy = null; + + #[EmbeddedSettings()] + public ?AttachmentsSettings $attachments = null; + + #[EmbeddedSettings()] + public ?HistorySettings $history = null; +} diff --git a/src/State/PartDBInfoProvider.php b/src/State/PartDBInfoProvider.php index c6760ede7..b3496cad2 100644 --- a/src/State/PartDBInfoProvider.php +++ b/src/State/PartDBInfoProvider.php @@ -9,6 +9,8 @@ use App\ApiResource\PartDBInfo; use App\Services\Misc\GitVersionInfo; use App\Services\System\BannerHelper; +use App\Settings\SystemSettings\CustomizationSettings; +use App\Settings\SystemSettings\LocalizationSettings; use Shivas\VersioningBundle\Service\VersionManagerInterface; class PartDBInfoProvider implements ProviderInterface @@ -16,12 +18,10 @@ class PartDBInfoProvider implements ProviderInterface public function __construct(private readonly VersionManagerInterface $versionManager, private readonly GitVersionInfo $gitVersionInfo, - private readonly string $partdb_title, - private readonly string $base_currency, private readonly BannerHelper $bannerHelper, private readonly string $default_uri, - private readonly string $global_timezone, - private readonly string $global_locale + private readonly LocalizationSettings $localizationSettings, + private readonly CustomizationSettings $customizationSettings, ) { @@ -33,12 +33,12 @@ public function provide(Operation $operation, array $uriVariables = [], array $c version: $this->versionManager->getVersion()->toString(), git_branch: $this->gitVersionInfo->getGitBranchName(), git_commit: $this->gitVersionInfo->getGitCommitHash(), - title: $this->partdb_title, + title: $this->customizationSettings->instanceName, banner: $this->bannerHelper->getBanner(), default_uri: $this->default_uri, - global_timezone: $this->global_timezone, - base_currency: $this->base_currency, - global_locale: $this->global_locale, + global_timezone: $this->localizationSettings->timezone, + base_currency: $this->localizationSettings->baseCurrency, + global_locale: $this->localizationSettings->locale, ); } } diff --git a/src/Twig/EntityExtension.php b/src/Twig/EntityExtension.php index 762ebb094..b5e5c3ca2 100644 --- a/src/Twig/EntityExtension.php +++ b/src/Twig/EntityExtension.php @@ -24,6 +24,7 @@ use App\Entity\Attachments\Attachment; use App\Entity\Base\AbstractDBElement; +use App\Entity\Parts\PartCustomState; use App\Entity\ProjectSystem\Project; use App\Entity\LabelSystem\LabelProfile; use App\Entity\Parts\Category; @@ -115,6 +116,7 @@ public function getEntityType(object $entity): ?string Currency::class => 'currency', MeasurementUnit::class => 'measurement_unit', LabelProfile::class => 'label_profile', + PartCustomState::class => 'part_custom_state', ]; foreach ($map as $class => $type) { diff --git a/src/Twig/FormatExtension.php b/src/Twig/FormatExtension.php index 76628ccd9..46313aaf4 100644 --- a/src/Twig/FormatExtension.php +++ b/src/Twig/FormatExtension.php @@ -82,7 +82,7 @@ public function amountFormat($value, ?MeasurementUnit $unit, array $options = [] public function formatBytes(int $bytes, int $precision = 2): string { $size = ['B','kB','MB','GB','TB','PB','EB','ZB','YB']; - $factor = floor((strlen((string) $bytes) - 1) / 3); + $factor = (int) floor((strlen((string) $bytes) - 1) / 3); //We use the real (10 based) SI prefix here return sprintf("%.{$precision}f", $bytes / (1000 ** $factor)) . ' ' . @$size[$factor]; } diff --git a/src/Twig/MiscExtension.php b/src/Twig/MiscExtension.php index 93762d35f..8b6ebc682 100644 --- a/src/Twig/MiscExtension.php +++ b/src/Twig/MiscExtension.php @@ -22,7 +22,11 @@ */ namespace App\Twig; +use App\Settings\SettingsIcon; use Symfony\Component\HttpFoundation\Request; +use App\Services\LogSystem\EventCommentType; +use Jbtronics\SettingsBundle\Proxy\SettingsProxyInterface; +use ReflectionClass; use Twig\TwigFunction; use App\Services\LogSystem\EventCommentNeededHelper; use Twig\Extension\AbstractExtension; @@ -36,14 +40,43 @@ public function __construct(private readonly EventCommentNeededHelper $eventComm public function getFunctions(): array { return [ - new TwigFunction('event_comment_needed', - fn(string $operation_type) => $this->eventCommentNeededHelper->isCommentNeeded($operation_type) - ), + new TwigFunction('event_comment_needed', $this->evenCommentNeeded(...)), + new TwigFunction('settings_icon', $this->settingsIcon(...)), new TwigFunction('uri_without_host', $this->uri_without_host(...)) ]; } + private function evenCommentNeeded(string|EventCommentType $operation_type): bool + { + if (is_string($operation_type)) { + $operation_type = EventCommentType::from($operation_type); + } + + return $this->eventCommentNeededHelper->isCommentNeeded($operation_type); + } + + /** + * Returns the value of the icon attribute of the SettingsIcon attribute of the given class. + * If the class does not have a SettingsIcon attribute, then null is returned. + * @param string|object $objectOrClass + * @return string|null + * @throws \ReflectionException + */ + private function settingsIcon(string|object $objectOrClass): ?string + { + //If the given object is a proxy, then get the real object + if (is_a($objectOrClass, SettingsProxyInterface::class)) { + $objectOrClass = get_parent_class($objectOrClass); + } + + $reflection = new ReflectionClass($objectOrClass); + + $attribute = $reflection->getAttributes(SettingsIcon::class)[0] ?? null; + + return $attribute?->newInstance()->icon; + } + /** * Similar to the getUri function of the request, but does not contain protocol and host. * @param Request $request diff --git a/src/Twig/Sandbox/InheritanceSecurityPolicy.php b/src/Twig/Sandbox/InheritanceSecurityPolicy.php index 93e874e95..06ab3a1f7 100644 --- a/src/Twig/Sandbox/InheritanceSecurityPolicy.php +++ b/src/Twig/Sandbox/InheritanceSecurityPolicy.php @@ -34,9 +34,14 @@ */ final class InheritanceSecurityPolicy implements SecurityPolicyInterface { + /** + * @var array + */ private array $allowedMethods; - public function __construct(private array $allowedTags = [], private array $allowedFilters = [], array $allowedMethods = [], private array $allowedProperties = [], private array $allowedFunctions = []) + public function __construct(private array $allowedTags = [], private array $allowedFilters = [], array $allowedMethods = [], + /** @var array */ + private array $allowedProperties = [], private array $allowedFunctions = []) { $this->setAllowedMethods($allowedMethods); } diff --git a/src/Twig/TwigCoreExtension.php b/src/Twig/TwigCoreExtension.php index 352e09d30..7b2b58f86 100644 --- a/src/Twig/TwigCoreExtension.php +++ b/src/Twig/TwigCoreExtension.php @@ -34,8 +34,11 @@ */ final class TwigCoreExtension extends AbstractExtension { - public function __construct(protected ObjectNormalizer $objectNormalizer) + private readonly ObjectNormalizer $objectNormalizer; + + public function __construct() { + $this->objectNormalizer = new ObjectNormalizer(); } public function getFunctions(): array diff --git a/src/Validator/Constraints/UniqueObjectCollection.php b/src/Validator/Constraints/UniqueObjectCollection.php index c71fcc5d0..6548494ee 100644 --- a/src/Validator/Constraints/UniqueObjectCollection.php +++ b/src/Validator/Constraints/UniqueObjectCollection.php @@ -43,12 +43,12 @@ class UniqueObjectCollection extends Constraint * @param array|string $fields the combination of fields that must contain unique values or a set of options */ public function __construct( - array $options = null, - string $message = null, - callable $normalizer = null, - array $groups = null, + ?array $options = null, + ?string $message = null, + ?callable $normalizer = null, + ?array $groups = null, mixed $payload = null, - array|string $fields = null, + array|string|null $fields = null, public bool $allowNull = true, ) { parent::__construct($options, $groups, $payload); diff --git a/src/Validator/Constraints/ValidGoogleAuthCode.php b/src/Validator/Constraints/ValidGoogleAuthCode.php index 482af35cd..180d346e7 100644 --- a/src/Validator/Constraints/ValidGoogleAuthCode.php +++ b/src/Validator/Constraints/ValidGoogleAuthCode.php @@ -31,8 +31,8 @@ class ValidGoogleAuthCode extends Constraint * @param TwoFactorInterface|null $user The user to use for the validation process, if null, the current user is used */ public function __construct( - array $options = null, - array $groups = null, + ?array $options = null, + ?array $groups = null, mixed $payload = null, public ?TwoFactorInterface $user = null) { diff --git a/symfony.lock b/symfony.lock index c7471b730..7c136b4b0 100644 --- a/symfony.lock +++ b/symfony.lock @@ -1,16 +1,16 @@ { - "api-platform/core": { - "version": "3.2", + "api-platform/symfony": { + "version": "4.1", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", - "version": "3.2", - "ref": "696d44adc3c0d4f5d25a2f1c4f3700dd8a5c6db9" + "version": "4.0", + "ref": "e9952e9f393c2d048f10a78f272cd35e807d972b" }, "files": [ - "config/packages/api_platform.yaml", - "config/routes/api_platform.yaml", - "src/ApiResource/.gitignore" + "./config/packages/api_platform.yaml", + "./config/routes/api_platform.yaml", + "./src/ApiResource/.gitignore" ] }, "beberlei/assert": { @@ -29,15 +29,15 @@ "version": "1.11.99.4" }, "dama/doctrine-test-bundle": { - "version": "8.0", + "version": "8.3", "recipe": { "repo": "github.com/symfony/recipes-contrib", "branch": "main", - "version": "7.2", - "ref": "896306d79d4ee143af9eadf9b09fd34a8c391b70" + "version": "8.3", + "ref": "dfc51177476fb39d014ed89944cde53dc3326d23" }, "files": [ - "./config/packages/dama_doctrine_test_bundle.yaml" + "config/packages/dama_doctrine_test_bundle.yaml" ] }, "doctrine/cache": { @@ -53,20 +53,26 @@ "version": "v2.9.2" }, "doctrine/deprecations": { - "version": "v0.5.3" + "version": "1.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "1.0", + "ref": "87424683adc81d7dc305eefec1fced883084aab9" + } }, "doctrine/doctrine-bundle": { - "version": "2.11", + "version": "2.15", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", - "version": "2.10", - "ref": "c170ded8fc587d6bd670550c43dafcf093762245" + "version": "2.13", + "ref": "620b57f496f2e599a6015a9fa222c2ee0a32adcb" }, "files": [ - "./config/packages/doctrine.yaml", - "./src/Entity/.gitignore", - "./src/Repository/.gitignore" + "config/packages/doctrine.yaml", + "src/Entity/.gitignore", + "src/Repository/.gitignore" ] }, "doctrine/doctrine-fixtures-bundle": { @@ -127,18 +133,6 @@ "ekino/phpstan-banned-code": { "version": "v0.3.1" }, - "erusev/parsedown": { - "version": "1.7.4" - }, - "florianv/exchanger": { - "version": "1.4.1" - }, - "florianv/swap": { - "version": "3.5.0" - }, - "florianv/swap-bundle": { - "version": "5.0.x-dev" - }, "gregwar/captcha": { "version": "v1.1.7" }, @@ -154,6 +148,9 @@ "jbtronics/dompdf-font-loader-bundle": { "version": "v1.1.1" }, + "jbtronics/settings-bundle": { + "version": "2.0.1" + }, "jbtronics/translation-editor-bundle": { "version": "v1.0" }, @@ -207,15 +204,15 @@ ] }, "nelmio/security-bundle": { - "version": "2.4", + "version": "3.5", "recipe": { "repo": "github.com/symfony/recipes", - "branch": "master", + "branch": "main", "version": "2.4", - "ref": "65726efb67ff51d89de38195bc0d230fa811f64d" + "ref": "71045833e4f882ad9de8c95fe47efb99a1eec2f7" }, "files": [ - "./config/packages/nelmio_security.yaml" + "config/packages/nelmio_security.yaml" ] }, "nikic/php-parser": { @@ -248,11 +245,8 @@ "./config/packages/datatables.yaml" ] }, - "phenx/php-font-lib": { - "version": "0.5.1" - }, - "phenx/php-svg-lib": { - "version": "v0.3.3" + "part-db/swap-bundle": { + "version": "v6.0.0" }, "php-http/discovery": { "version": "1.18", @@ -306,17 +300,18 @@ "version": "0.12.4" }, "phpunit/phpunit": { - "version": "9.6", + "version": "11.5", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", - "version": "9.6", - "ref": "7364a21d87e658eb363c5020c072ecfdc12e2326" + "version": "11.1", + "ref": "c6658a60fc9d594805370eacdf542c3d6b5c0869" }, "files": [ - "./.env.test", - "./phpunit.xml.dist", - "./tests/bootstrap.php" + ".env.test", + "bin/phpunit", + "phpunit.xml.dist", + "tests/bootstrap.php" ] }, "psr/cache": { @@ -386,10 +381,10 @@ "repo": "github.com/symfony/recipes-contrib", "branch": "main", "version": "1.0", - "ref": "0f18b4decdf5695d692c1d0dfd65516a07a6adf1" + "ref": "5d454ec6cc4c700ed3d963f3803e1d427d9669fb" }, "files": [ - "./public/.htaccess" + "public/.htaccess" ] }, "symfony/asset": { @@ -481,17 +476,27 @@ ] }, "symfony/form": { - "version": "v4.2.3" + "version": "7.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "7.2", + "ref": "7d86a6723f4a623f59e2bf966b6aad2fc461d36b" + }, + "files": [ + "./config/packages/csrf.yaml" + ] }, "symfony/framework-bundle": { - "version": "6.4", + "version": "7.3", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", - "version": "6.4", - "ref": "a91c965766ad3ff2ae15981801643330eb42b6a5" + "version": "7.3", + "ref": "5a1497d539f691b96afd45ae397ce5fe30beb4b9" }, "files": [ + ".editorconfig", "config/packages/cache.yaml", "config/packages/framework.yaml", "config/preload.php", @@ -518,15 +523,15 @@ "version": "v4.2.3" }, "symfony/mailer": { - "version": "6.4", + "version": "7.3", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", "version": "4.3", - "ref": "df66ee1f226c46f01e85c29c2f7acce0596ba35a" + "ref": "09051cfde49476e3c12cd3a0e44289ace1c75a4f" }, "files": [ - "./config/packages/mailer.yaml" + "config/packages/mailer.yaml" ] }, "symfony/maker-bundle": { @@ -563,19 +568,14 @@ "version": "v5.3.8" }, "symfony/phpunit-bridge": { - "version": "6.4", + "version": "7.3", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", - "version": "6.3", - "ref": "a411a0480041243d97382cac7984f7dce7813c08" + "version": "7.3", + "ref": "dc13fec96bd527bd399c3c01f0aab915c67fd544" }, - "files": [ - "./.env.test", - "./bin/phpunit", - "./phpunit.xml.dist", - "./tests/bootstrap.php" - ] + "files": [] }, "symfony/polyfill-ctype": { "version": "v1.14.0" @@ -605,15 +605,24 @@ "version": "v4.2.3" }, "symfony/property-info": { - "version": "v4.2.3" + "version": "7.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "7.3", + "ref": "dae70df71978ae9226ae915ffd5fad817f5ca1f7" + }, + "files": [ + "./config/packages/property_info.yaml" + ] }, "symfony/routing": { - "version": "6.2", + "version": "7.3", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", - "version": "6.2", - "ref": "e0a11b4ccb8c9e70b574ff5ad3dfdcd41dec5aa6" + "version": "7.0", + "ref": "21b72649d5622d8f7da329ffb5afb232a023619d" }, "files": [ "config/packages/routing.yaml", @@ -652,17 +661,18 @@ "version": "v1.1.5" }, "symfony/stimulus-bundle": { - "version": "2.16", + "version": "2.27", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", - "version": "2.13", - "ref": "6acd9ff4f7fd5626d2962109bd4ebab351d43c43" + "version": "2.20", + "ref": "e058471c5502e549c1404ebdd510099107bb5549" }, "files": [ - "./assets/bootstrap.js", - "./assets/controllers.json", - "./assets/controllers/hello_controller.js" + "assets/bootstrap.js", + "assets/controllers.json", + "assets/controllers/csrf_protection_controller.js", + "assets/controllers/hello_controller.js" ] }, "symfony/stopwatch": { @@ -672,16 +682,16 @@ "version": "v5.1.0" }, "symfony/translation": { - "version": "6.4", + "version": "7.3", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", "version": "6.3", - "ref": "e28e27f53663cc34f0be2837aba18e3a1bef8e7b" + "ref": "620a1b84865ceb2ba304c8f8bf2a185fbf32a843" }, "files": [ - "./config/packages/translation.yaml", - "./translations/.gitignore" + "config/packages/translation.yaml", + "translations/.gitignore" ] }, "symfony/translation-contracts": { @@ -704,16 +714,14 @@ ] }, "symfony/uid": { - "version": "6.2", + "version": "7.3", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", - "version": "6.2", - "ref": "d294ad4add3e15d7eb1bae0221588ca89b38e558" + "version": "7.0", + "ref": "0df5844274d871b37fc3816c57a768ffc60a43a5" }, - "files": [ - "./config/packages/uid.yaml" - ] + "files": [] }, "symfony/ux-translator": { "version": "2.9", @@ -731,15 +739,24 @@ ] }, "symfony/ux-turbo": { - "version": "v2.16.0" + "version": "2.28", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "2.20", + "ref": "287f7c6eb6e9b65e422d34c00795b360a787380b" + }, + "files": [ + "config/packages/ux_turbo.yaml" + ] }, "symfony/validator": { - "version": "5.4", + "version": "7.3", "recipe": { "repo": "github.com/symfony/recipes", - "branch": "master", - "version": "5.3", - "ref": "c32cfd98f714894c4f128bb99aa2530c1227603c" + "branch": "main", + "version": "7.0", + "ref": "8c1c4e28d26a124b0bb273f537ca8ce443472bfd" }, "files": [ "config/packages/validator.yaml" @@ -755,12 +772,12 @@ "version": "v4.2.3" }, "symfony/web-profiler-bundle": { - "version": "6.3", + "version": "7.3", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", - "version": "6.1", - "ref": "e42b3f0177df239add25373083a564e5ead4e13a" + "version": "7.3", + "ref": "a363460c1b0b4a4d0242f2ce1a843ca0f6ac9026" }, "files": [ "config/packages/web_profiler.yaml", @@ -768,12 +785,12 @@ ] }, "symfony/webpack-encore-bundle": { - "version": "2.1", + "version": "2.2", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", "version": "2.0", - "ref": "082d754b3bd54b3fc669f278f1eea955cfd23cf5" + "ref": "9ef5412a4a2a8415aca3a3f2b4edd3866aab9a19" }, "files": [ "assets/app.js", @@ -786,9 +803,6 @@ "symfony/yaml": { "version": "v4.2.3" }, - "symplify/easy-coding-standard": { - "version": "v7.1.3" - }, "tecnickcom/tc-lib-barcode": { "version": "1.15.20" }, diff --git a/templates/_navbar.html.twig b/templates/_navbar.html.twig index cd1f641f1..446ccdab5 100644 --- a/templates/_navbar.html.twig +++ b/templates/_navbar.html.twig @@ -1,4 +1,5 @@ {% import "helper.twig" as helper %} +{% import "vars.macro.twig" as vars %} {% import "components/search.macro.html.twig" as search %}